@atlaskit/jql-editor 6.4.6 → 6.5.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 +20 -0
- package/dist/cjs/analytics/util.js +1 -1
- package/dist/cjs/plugins/rich-inline-nodes/nodes/index.js +2 -0
- package/dist/cjs/plugins/rich-inline-nodes/nodes/lozenge-with-avatar/index.js +41 -0
- package/dist/cjs/plugins/rich-inline-nodes/nodes/lozenge-with-avatar/styled.js +105 -0
- package/dist/cjs/plugins/rich-inline-nodes/util/replace-nodes-transaction.js +86 -33
- package/dist/cjs/state/hydration/index.js +15 -15
- package/dist/cjs/state/hydration/util.js +6 -6
- package/dist/cjs/state/index.js +40 -31
- package/dist/cjs/ui/jql-editor-controls-content/base-syntax-help/styled.js +1 -1
- package/dist/cjs/ui/jql-editor-layout/styled.js +1 -1
- package/dist/cjs/ui/jql-editor-view/index.js +1 -2
- package/dist/es2019/analytics/util.js +1 -1
- package/dist/es2019/plugins/rich-inline-nodes/nodes/index.js +2 -0
- package/dist/es2019/plugins/rich-inline-nodes/nodes/lozenge-with-avatar/index.js +32 -0
- package/dist/es2019/plugins/rich-inline-nodes/nodes/lozenge-with-avatar/styled.js +97 -0
- package/dist/es2019/plugins/rich-inline-nodes/util/replace-nodes-transaction.js +63 -15
- package/dist/es2019/state/hydration/util.js +6 -6
- package/dist/es2019/state/index.js +12 -2
- package/dist/es2019/ui/jql-editor-controls-content/base-syntax-help/styled.js +1 -1
- package/dist/es2019/ui/jql-editor-layout/styled.js +1 -1
- package/dist/es2019/ui/jql-editor-view/index.js +1 -4
- package/dist/esm/analytics/util.js +1 -1
- package/dist/esm/plugins/rich-inline-nodes/nodes/index.js +2 -0
- package/dist/esm/plugins/rich-inline-nodes/nodes/lozenge-with-avatar/index.js +34 -0
- package/dist/esm/plugins/rich-inline-nodes/nodes/lozenge-with-avatar/styled.js +96 -0
- package/dist/esm/plugins/rich-inline-nodes/util/replace-nodes-transaction.js +86 -33
- package/dist/esm/state/hydration/index.js +15 -15
- package/dist/esm/state/hydration/util.js +6 -6
- package/dist/esm/state/index.js +39 -30
- package/dist/esm/ui/jql-editor-controls-content/base-syntax-help/styled.js +1 -1
- package/dist/esm/ui/jql-editor-layout/styled.js +1 -1
- package/dist/esm/ui/jql-editor-view/index.js +1 -2
- package/dist/types/plugins/rich-inline-nodes/nodes/index.d.ts +2 -0
- package/dist/types/plugins/rich-inline-nodes/nodes/lozenge-with-avatar/index.d.ts +7 -0
- package/dist/types/plugins/rich-inline-nodes/nodes/lozenge-with-avatar/styled.d.ts +18 -0
- package/dist/types/state/index.d.ts +5 -1
- package/dist/types/types.d.ts +1 -1
- package/dist/types/ui/jql-editor/types.d.ts +7 -1
- package/dist/types/ui/types.d.ts +1 -1
- package/dist/types-ts4.5/plugins/rich-inline-nodes/nodes/index.d.ts +2 -0
- package/dist/types-ts4.5/plugins/rich-inline-nodes/nodes/lozenge-with-avatar/index.d.ts +7 -0
- package/dist/types-ts4.5/plugins/rich-inline-nodes/nodes/lozenge-with-avatar/styled.d.ts +18 -0
- package/dist/types-ts4.5/state/index.d.ts +5 -1
- package/dist/types-ts4.5/types.d.ts +1 -1
- package/dist/types-ts4.5/ui/jql-editor/types.d.ts +7 -1
- package/dist/types-ts4.5/ui/types.d.ts +1 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @atlaskit/jql-editor
|
|
2
2
|
|
|
3
|
+
## 6.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`9ead91bedc94b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/9ead91bedc94b) -
|
|
8
|
+
[ux] Add function argument hydration support behind feature flag
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- Updated dependencies
|
|
13
|
+
|
|
14
|
+
## 6.4.7
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- [`87ab60401cc1d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/87ab60401cc1d) -
|
|
19
|
+
Cleanup feature gate `empanda_jql-editor_fix_tab_select_in_popup`. The JQL editor input now always
|
|
20
|
+
sets `tabindex="0"` so it is reliably focusable when rendered inside a popup.
|
|
21
|
+
- Updated dependencies
|
|
22
|
+
|
|
3
23
|
## 6.4.6
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -6,5 +6,5 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.useJqlEditorAnalytics = void 0;
|
|
7
7
|
var _jqlEditorCommon = require("@atlaskit/jql-editor-common");
|
|
8
8
|
var useJqlEditorAnalytics = exports.useJqlEditorAnalytics = function useJqlEditorAnalytics(analyticsSource) {
|
|
9
|
-
return (0, _jqlEditorCommon.useJqlPackageAnalytics)(analyticsSource, "@atlaskit/jql-editor", "6.4.
|
|
9
|
+
return (0, _jqlEditorCommon.useJqlPackageAnalytics)(analyticsSource, "@atlaskit/jql-editor", "6.4.7", _jqlEditorCommon.ANALYTICS_CHANNEL);
|
|
10
10
|
};
|
|
@@ -5,10 +5,12 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.richInlineNodes = void 0;
|
|
7
7
|
var _goal = require("./goal");
|
|
8
|
+
var _lozengeWithAvatar = require("./lozenge-with-avatar");
|
|
8
9
|
var _project = require("./project");
|
|
9
10
|
var _team = require("./team");
|
|
10
11
|
var _user = require("./user");
|
|
11
12
|
var richInlineNodes = exports.richInlineNodes = {
|
|
13
|
+
lozengeWithAvatar: _lozengeWithAvatar.lozengeWithAvatar,
|
|
12
14
|
user: _user.user,
|
|
13
15
|
team: _team.team,
|
|
14
16
|
project: _project.project,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.lozengeWithAvatar = void 0;
|
|
8
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
9
|
+
var _react = _interopRequireDefault(require("react"));
|
|
10
|
+
var _avatar = _interopRequireDefault(require("@atlaskit/avatar"));
|
|
11
|
+
var _state = require("../../../../state");
|
|
12
|
+
var _styled = require("./styled");
|
|
13
|
+
var LozengeWithAvatar = function LozengeWithAvatar(_ref) {
|
|
14
|
+
var id = _ref.id,
|
|
15
|
+
name = _ref.name,
|
|
16
|
+
fieldName = _ref.fieldName,
|
|
17
|
+
selected = _ref.selected,
|
|
18
|
+
error = _ref.error;
|
|
19
|
+
var _useHydratedLozengeWi = (0, _state.useHydratedLozengeWithAvatar)({
|
|
20
|
+
id: id,
|
|
21
|
+
fieldName: fieldName
|
|
22
|
+
}),
|
|
23
|
+
_useHydratedLozengeWi2 = (0, _slicedToArray2.default)(_useHydratedLozengeWi, 1),
|
|
24
|
+
lozengeWithAvatar = _useHydratedLozengeWi2[0];
|
|
25
|
+
return /*#__PURE__*/_react.default.createElement(_styled.LozengeWithAvatarContainer, {
|
|
26
|
+
selected: selected,
|
|
27
|
+
error: error
|
|
28
|
+
}, /*#__PURE__*/_react.default.createElement(_styled.AvatarWrapper, null, /*#__PURE__*/_react.default.createElement(_avatar.default, {
|
|
29
|
+
src: lozengeWithAvatar === null || lozengeWithAvatar === void 0 ? void 0 : lozengeWithAvatar.avatarUrl,
|
|
30
|
+
borderColor: "transparent",
|
|
31
|
+
size: "xsmall"
|
|
32
|
+
})), /*#__PURE__*/_react.default.createElement(_styled.NameContainer, null, name));
|
|
33
|
+
};
|
|
34
|
+
var lozengeWithAvatar = exports.lozengeWithAvatar = {
|
|
35
|
+
component: LozengeWithAvatar,
|
|
36
|
+
attrs: {
|
|
37
|
+
id: {},
|
|
38
|
+
name: {},
|
|
39
|
+
fieldName: {}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.NameContainer = exports.LozengeWithAvatarContainer = exports.AvatarWrapper = void 0;
|
|
8
|
+
var _react = require("@emotion/react");
|
|
9
|
+
var _styled = _interopRequireDefault(require("@emotion/styled"));
|
|
10
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-exported-styles -- Ignored via go/DSP-18766
|
|
17
|
+
var LozengeWithAvatarContainer = exports.LozengeWithAvatarContainer =
|
|
18
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-styled -- Ignored via go/DSP-18766
|
|
19
|
+
_styled.default.span({
|
|
20
|
+
display: 'inline-flex',
|
|
21
|
+
alignItems: 'baseline',
|
|
22
|
+
paddingLeft: "var(--ds-space-025, 2px)",
|
|
23
|
+
borderRadius: "var(--ds-radius-xlarge, 12px)",
|
|
24
|
+
cursor: 'pointer',
|
|
25
|
+
userSelect: 'none'
|
|
26
|
+
},
|
|
27
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-dynamic-styles -- Ignored via go/DSP-18766
|
|
28
|
+
function (_ref) {
|
|
29
|
+
var selected = _ref.selected,
|
|
30
|
+
error = _ref.error;
|
|
31
|
+
if (selected) {
|
|
32
|
+
if (error) {
|
|
33
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
|
|
34
|
+
return (0, _react.css)({
|
|
35
|
+
color: "var(--ds-text-inverse, #FFFFFF)",
|
|
36
|
+
backgroundColor: "var(--ds-background-danger-bold, #C9372C)",
|
|
37
|
+
textDecoration: 'wavy underline',
|
|
38
|
+
textDecorationThickness: '1px',
|
|
39
|
+
textDecorationSkipInk: 'none',
|
|
40
|
+
textDecorationColor: "var(--ds-text-inverse, #FFFFFF)"
|
|
41
|
+
});
|
|
42
|
+
} else {
|
|
43
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
|
|
44
|
+
return (0, _react.css)({
|
|
45
|
+
color: "var(--ds-text, #292A2E)",
|
|
46
|
+
backgroundColor: "var(--ds-background-selected, #E9F2FE)",
|
|
47
|
+
boxShadow: "0 0 0 1px ".concat("var(--ds-border-selected, #1868DB)")
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
if (error) {
|
|
52
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
|
|
53
|
+
return (0, _react.css)({
|
|
54
|
+
color: "var(--ds-text-subtle, #505258)",
|
|
55
|
+
backgroundColor: "var(--ds-background-neutral, #0515240F)",
|
|
56
|
+
textDecoration: 'wavy underline',
|
|
57
|
+
textDecorationThickness: '1px',
|
|
58
|
+
textDecorationSkipInk: 'none',
|
|
59
|
+
textDecorationColor: "var(--ds-text-danger, #AE2E24)",
|
|
60
|
+
'&:hover': {
|
|
61
|
+
backgroundColor: "var(--ds-background-neutral-hovered, #0B120E24)"
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
|
|
66
|
+
return (0, _react.css)({
|
|
67
|
+
color: "var(--ds-text-subtle, #505258)",
|
|
68
|
+
backgroundColor: "var(--ds-background-neutral, #0515240F)",
|
|
69
|
+
'&:hover': {
|
|
70
|
+
backgroundColor: "var(--ds-background-neutral-hovered, #0B120E24)"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-exported-styles -- Ignored via go/DSP-18766
|
|
78
|
+
var NameContainer = exports.NameContainer =
|
|
79
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-styled -- Ignored via go/DSP-18766
|
|
80
|
+
_styled.default.span({
|
|
81
|
+
marginLeft: "var(--ds-space-075, 6px)",
|
|
82
|
+
marginRight: "var(--ds-space-100, 8px)",
|
|
83
|
+
// eslint-disable-next-line -- Ignored via go/DSP-18766
|
|
84
|
+
lineHeight: "var(--ds-space-250, 20px)"
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/* Override Avatar styles to match design spec */
|
|
88
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-exported-styles -- Ignored via go/DSP-18766
|
|
89
|
+
var AvatarWrapper = exports.AvatarWrapper =
|
|
90
|
+
// eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage, @atlaskit/ui-styling-standard/no-styled -- Ignored via go/DSP-18766
|
|
91
|
+
_styled.default.div({
|
|
92
|
+
height: "var(--ds-space-200, 16px)",
|
|
93
|
+
width: "var(--ds-space-200, 16px)",
|
|
94
|
+
alignSelf: 'center',
|
|
95
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
|
|
96
|
+
'> div span': {
|
|
97
|
+
margin: "var(--ds-space-0, 0px)"
|
|
98
|
+
},
|
|
99
|
+
// Fix fallback avatar icon vertical alignment.
|
|
100
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766
|
|
101
|
+
'> div > span:has(> span)': {
|
|
102
|
+
position: 'relative',
|
|
103
|
+
top: '-2px'
|
|
104
|
+
}
|
|
105
|
+
});
|
|
@@ -5,13 +5,13 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
7
|
exports.replaceRichInlineNodes = void 0;
|
|
8
|
-
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
9
8
|
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
10
9
|
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
11
10
|
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
|
|
12
11
|
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
|
|
13
12
|
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
|
|
14
13
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
14
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
15
15
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
16
16
|
var _featureGateJsClient = _interopRequireDefault(require("@atlaskit/feature-gate-js-client"));
|
|
17
17
|
var _jqlAst = require("@atlaskit/jql-ast");
|
|
@@ -34,32 +34,42 @@ var replaceRichInlineNodes = exports.replaceRichInlineNodes = function replaceRi
|
|
|
34
34
|
fieldName = _ref2[0],
|
|
35
35
|
values = _ref2[1];
|
|
36
36
|
values.forEach(function (value) {
|
|
37
|
-
if (
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// If not found as direct value and it's a team, try to find in membersOf function arguments
|
|
42
|
-
if (astNodes.length === 0 && value.type === 'team' && (0, _platformFeatureFlags.fg)('jira-membersof-team-support')) {
|
|
43
|
-
astNodes = getMembersOfArgumentNodes(ast, value.id);
|
|
37
|
+
if ((0, _platformFeatureFlags.fg)('jql-function-arg-hydration')) {
|
|
38
|
+
// Skip deprecated fields
|
|
39
|
+
if (value.type === 'deprecated-field') {
|
|
40
|
+
return;
|
|
44
41
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
});
|
|
42
|
+
// When the gate is on, collect both direct value operands and function argument
|
|
43
|
+
// operands for all node types
|
|
44
|
+
var astNodes = [].concat((0, _toConsumableArray2.default)(getValueNodes(ast, fieldName, value.id)), (0, _toConsumableArray2.default)(getFunctionArgumentNodes(ast, fieldName, value.id)));
|
|
45
|
+
replaceAstNodesWithRichInlineNodes(transaction, astNodes, fieldName, value);
|
|
46
|
+
} else if (value.type === 'user' || value.type === 'team' || value.type === 'goal' && _featureGateJsClient.default.getExperimentValue('anip-1095-goals-in-harmonised-filter', 'isEnabled', false) || value.type === 'project' && _featureGateJsClient.default.getExperimentValue('atlassian_projects_-_native_integration', 'releaseVersion', -1) >= 1) {
|
|
47
|
+
// Legacy path: direct value operands only, with membersOf fallback for teams.
|
|
48
|
+
var _astNodes = getValueNodes(ast, fieldName, value.id);
|
|
49
|
+
if (_astNodes.length === 0 && value.type === 'team' && (0, _platformFeatureFlags.fg)('jira-membersof-team-support')) {
|
|
50
|
+
_astNodes = getMembersOfArgumentNodes(ast, value.id);
|
|
51
|
+
}
|
|
52
|
+
replaceAstNodesWithRichInlineNodes(transaction, _astNodes, fieldName, value);
|
|
58
53
|
}
|
|
59
54
|
});
|
|
60
55
|
});
|
|
61
56
|
return transaction;
|
|
62
57
|
};
|
|
58
|
+
var replaceAstNodesWithRichInlineNodes = function replaceAstNodesWithRichInlineNodes(transaction, astNodes, fieldName, value) {
|
|
59
|
+
astNodes.forEach(function (astNode) {
|
|
60
|
+
if (astNode.position) {
|
|
61
|
+
var _astNode$position = (0, _slicedToArray2.default)(astNode.position, 2),
|
|
62
|
+
from = _astNode$position[0],
|
|
63
|
+
to = _astNode$position[1];
|
|
64
|
+
var documentFrom = (0, _getDocumentPosition.default)(transaction.doc, from);
|
|
65
|
+
if (!isRichInlineNode(transaction.doc, documentFrom)) {
|
|
66
|
+
var documentTo = (0, _getDocumentPosition.default)(transaction.doc, to);
|
|
67
|
+
var node = getRichInlineNode(fieldName, value, astNode.text);
|
|
68
|
+
transaction.replaceWith(documentFrom, documentTo, node);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
};
|
|
63
73
|
var getRichInlineNode = function getRichInlineNode(fieldName, value, text) {
|
|
64
74
|
switch (value.type) {
|
|
65
75
|
case 'user':
|
|
@@ -90,6 +100,13 @@ var getRichInlineNode = function getRichInlineNode(fieldName, value, text) {
|
|
|
90
100
|
fieldName: fieldName
|
|
91
101
|
}), _textContent3);
|
|
92
102
|
}
|
|
103
|
+
case 'lozengeWithAvatar':
|
|
104
|
+
{
|
|
105
|
+
var _textContent4 = _schema.JQLEditorSchema.text(text);
|
|
106
|
+
return _schema.JQLEditorSchema.nodes.lozengeWithAvatar.create(_objectSpread(_objectSpread({}, value), {}, {
|
|
107
|
+
fieldName: fieldName
|
|
108
|
+
}), _textContent4);
|
|
109
|
+
}
|
|
93
110
|
default:
|
|
94
111
|
{
|
|
95
112
|
throw new Error("Unsupported hydrated value type ".concat(value.type));
|
|
@@ -112,6 +129,12 @@ var getMembersOfArgumentNodes = function getMembersOfArgumentNodes(ast, teamId)
|
|
|
112
129
|
}
|
|
113
130
|
return ast.query.accept(new FindMembersOfArgumentsVisitor(teamId));
|
|
114
131
|
};
|
|
132
|
+
var getFunctionArgumentNodes = function getFunctionArgumentNodes(ast, fieldName, valueId) {
|
|
133
|
+
if (!ast.query) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
return ast.query.accept(new FindFunctionArgumentsVisitor(fieldName, valueId));
|
|
137
|
+
};
|
|
115
138
|
|
|
116
139
|
/**
|
|
117
140
|
* Base visitor class for traversing JQL AST to find specific nodes.
|
|
@@ -198,21 +221,51 @@ var FindValuesVisitor = /*#__PURE__*/function (_BaseAstNodeFinder2) {
|
|
|
198
221
|
return (0, _createClass2.default)(FindValuesVisitor);
|
|
199
222
|
}(BaseAstNodeFinder);
|
|
200
223
|
/**
|
|
201
|
-
* Visitor that finds
|
|
202
|
-
*
|
|
224
|
+
* Visitor that finds function arguments for a specific field matching a target value id.
|
|
225
|
+
* This visitor is field-aware: it processes clauses for the given field and matches arguments by their raw value.
|
|
203
226
|
*/
|
|
204
|
-
var
|
|
205
|
-
function
|
|
227
|
+
var FindFunctionArgumentsVisitor = /*#__PURE__*/function (_BaseAstNodeFinder3) {
|
|
228
|
+
function FindFunctionArgumentsVisitor(fieldName, targetValueId) {
|
|
206
229
|
var _this3;
|
|
207
|
-
(0, _classCallCheck2.default)(this,
|
|
208
|
-
_this3 = _callSuper(this,
|
|
230
|
+
(0, _classCallCheck2.default)(this, FindFunctionArgumentsVisitor);
|
|
231
|
+
_this3 = _callSuper(this, FindFunctionArgumentsVisitor);
|
|
209
232
|
(0, _defineProperty2.default)(_this3, "visitTerminalClause", function (terminalClause) {
|
|
233
|
+
if (!_this3.equalsIgnoreCase(terminalClause.field.value, _this3.fieldName)) {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
210
236
|
if (terminalClause.operand === undefined) {
|
|
211
237
|
return [];
|
|
212
238
|
}
|
|
213
239
|
return terminalClause.operand.accept(_this3);
|
|
214
240
|
});
|
|
215
241
|
(0, _defineProperty2.default)(_this3, "visitFunctionOperand", function (functionOperand) {
|
|
242
|
+
return functionOperand.arguments.filter(function (arg) {
|
|
243
|
+
return _this3.equalsIgnoreCase(arg.value.trim(), _this3.targetValueId);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
_this3.fieldName = fieldName;
|
|
247
|
+
_this3.targetValueId = targetValueId.trim();
|
|
248
|
+
return _this3;
|
|
249
|
+
}
|
|
250
|
+
(0, _inherits2.default)(FindFunctionArgumentsVisitor, _BaseAstNodeFinder3);
|
|
251
|
+
return (0, _createClass2.default)(FindFunctionArgumentsVisitor);
|
|
252
|
+
}(BaseAstNodeFinder);
|
|
253
|
+
/**
|
|
254
|
+
* Visitor that finds membersOf function arguments matching a specific team ID.
|
|
255
|
+
* Used for queries like "assignee in membersOf("id: <uuid>")".
|
|
256
|
+
*/
|
|
257
|
+
var FindMembersOfArgumentsVisitor = /*#__PURE__*/function (_BaseAstNodeFinder4) {
|
|
258
|
+
function FindMembersOfArgumentsVisitor(teamId) {
|
|
259
|
+
var _this4;
|
|
260
|
+
(0, _classCallCheck2.default)(this, FindMembersOfArgumentsVisitor);
|
|
261
|
+
_this4 = _callSuper(this, FindMembersOfArgumentsVisitor);
|
|
262
|
+
(0, _defineProperty2.default)(_this4, "visitTerminalClause", function (terminalClause) {
|
|
263
|
+
if (terminalClause.operand === undefined) {
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
return terminalClause.operand.accept(_this4);
|
|
267
|
+
});
|
|
268
|
+
(0, _defineProperty2.default)(_this4, "visitFunctionOperand", function (functionOperand) {
|
|
216
269
|
var functionName = functionOperand.function.value.toLowerCase();
|
|
217
270
|
|
|
218
271
|
// Only process membersOf function
|
|
@@ -223,18 +276,18 @@ var FindMembersOfArgumentsVisitor = /*#__PURE__*/function (_BaseAstNodeFinder3)
|
|
|
223
276
|
functionOperand.arguments.forEach(function (arg) {
|
|
224
277
|
// Normalize both values by removing extra whitespace for comparison
|
|
225
278
|
// This handles both "id: uuid" and "id:uuid" formats
|
|
226
|
-
var normalizedArgValue =
|
|
227
|
-
var normalizedTeamId =
|
|
228
|
-
if (
|
|
279
|
+
var normalizedArgValue = _this4.normalizeValue(arg.value);
|
|
280
|
+
var normalizedTeamId = _this4.normalizeValue(_this4.teamId);
|
|
281
|
+
if (_this4.equalsIgnoreCase(normalizedArgValue, normalizedTeamId)) {
|
|
229
282
|
matchingArgs.push(arg);
|
|
230
283
|
}
|
|
231
284
|
});
|
|
232
285
|
return matchingArgs;
|
|
233
286
|
});
|
|
234
|
-
|
|
235
|
-
return
|
|
287
|
+
_this4.teamId = teamId;
|
|
288
|
+
return _this4;
|
|
236
289
|
}
|
|
237
|
-
(0, _inherits2.default)(FindMembersOfArgumentsVisitor,
|
|
290
|
+
(0, _inherits2.default)(FindMembersOfArgumentsVisitor, _BaseAstNodeFinder4);
|
|
238
291
|
return (0, _createClass2.default)(FindMembersOfArgumentsVisitor, [{
|
|
239
292
|
key: "normalizeValue",
|
|
240
293
|
value:
|
|
@@ -19,19 +19,19 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
19
19
|
var hydrateQuery = exports.hydrateQuery = function hydrateQuery() {
|
|
20
20
|
return /*#__PURE__*/function () {
|
|
21
21
|
var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(_ref, _ref2) {
|
|
22
|
-
var setState, getState, dispatch, onHydrate, _getState, query, oldHydratedValues, enableRichInlineNodes, editorState, queryToHydrate, jast, visitor, newHydratedValues, hydratedValuesMap;
|
|
23
|
-
return _regenerator.default.wrap(function
|
|
22
|
+
var setState, getState, dispatch, onHydrate, _getState, query, oldHydratedValues, enableRichInlineNodes, editorState, queryToHydrate, jast, visitor, newHydratedValues, hydratedValuesMap, _t;
|
|
23
|
+
return _regenerator.default.wrap(function (_context) {
|
|
24
24
|
while (1) switch (_context.prev = _context.next) {
|
|
25
25
|
case 0:
|
|
26
26
|
setState = _ref.setState, getState = _ref.getState, dispatch = _ref.dispatch;
|
|
27
27
|
onHydrate = _ref2.onHydrate;
|
|
28
28
|
_getState = getState(), query = _getState.query, oldHydratedValues = _getState.hydratedValues, enableRichInlineNodes = _getState.enableRichInlineNodes, editorState = _getState.editorState;
|
|
29
29
|
if (!(!enableRichInlineNodes || !onHydrate || !query)) {
|
|
30
|
-
_context.next =
|
|
30
|
+
_context.next = 1;
|
|
31
31
|
break;
|
|
32
32
|
}
|
|
33
33
|
return _context.abrupt("return");
|
|
34
|
-
case
|
|
34
|
+
case 1:
|
|
35
35
|
queryToHydrate = query;
|
|
36
36
|
jast = (0, _jqlAst.getJastFromState)(editorState); // Hydration API will fail for syntactically invalid queries, including partial queries. We do want to hydrate those
|
|
37
37
|
// as well to be able to handle queries like `assignee in (abc-123-def`, so we build an equivalent valid query.
|
|
@@ -40,15 +40,15 @@ var hydrateQuery = exports.hydrateQuery = function hydrateQuery() {
|
|
|
40
40
|
queryToHydrate = jast.query.accept(visitor);
|
|
41
41
|
}
|
|
42
42
|
if (queryToHydrate) {
|
|
43
|
-
_context.next =
|
|
43
|
+
_context.next = 2;
|
|
44
44
|
break;
|
|
45
45
|
}
|
|
46
46
|
return _context.abrupt("return");
|
|
47
|
-
case
|
|
48
|
-
_context.prev =
|
|
49
|
-
_context.next =
|
|
47
|
+
case 2:
|
|
48
|
+
_context.prev = 2;
|
|
49
|
+
_context.next = 3;
|
|
50
50
|
return onHydrate(queryToHydrate);
|
|
51
|
-
case
|
|
51
|
+
case 3:
|
|
52
52
|
newHydratedValues = _context.sent;
|
|
53
53
|
// IMPORTANT: Field name keys must be normalised (unquoted + lowercased) when storing hydrated values.
|
|
54
54
|
// The hydration API returns field names in a canonical format (e.g. 'Project[AtlassianProject]'),
|
|
@@ -70,16 +70,16 @@ var hydrateQuery = exports.hydrateQuery = function hydrateQuery() {
|
|
|
70
70
|
hydratedValues: hydratedValuesMap
|
|
71
71
|
});
|
|
72
72
|
dispatch(replaceHydratedValuesWithRichInlineNodes());
|
|
73
|
-
_context.next =
|
|
73
|
+
_context.next = 5;
|
|
74
74
|
break;
|
|
75
|
-
case
|
|
76
|
-
_context.prev =
|
|
77
|
-
|
|
78
|
-
case
|
|
75
|
+
case 4:
|
|
76
|
+
_context.prev = 4;
|
|
77
|
+
_t = _context["catch"](2);
|
|
78
|
+
case 5:
|
|
79
79
|
case "end":
|
|
80
80
|
return _context.stop();
|
|
81
81
|
}
|
|
82
|
-
}, _callee, null, [[
|
|
82
|
+
}, _callee, null, [[2, 4]]);
|
|
83
83
|
}));
|
|
84
84
|
return function (_x, _x2) {
|
|
85
85
|
return _ref3.apply(this, arguments);
|
|
@@ -85,16 +85,16 @@ var ValidQueryVisitor = exports.ValidQueryVisitor = /*#__PURE__*/function (_Abst
|
|
|
85
85
|
}).join(', '), ")");
|
|
86
86
|
});
|
|
87
87
|
(0, _defineProperty2.default)(_this, "visitFunctionOperand", function (functionOperand) {
|
|
88
|
-
// Only include membersOf function as it has arguments that need hydration
|
|
89
|
-
// Other functions like currentUser() don't have hydratable arguments
|
|
90
88
|
var functionName = functionOperand.function.value.toLowerCase();
|
|
91
|
-
if (functionName !== 'membersof' || !(0, _platformFeatureFlags.fg)('jira-membersof-team-support')) {
|
|
92
|
-
return '';
|
|
93
|
-
}
|
|
94
89
|
var args = functionOperand.arguments.map(function (arg) {
|
|
95
90
|
return arg.text;
|
|
96
91
|
}).join(', ');
|
|
97
|
-
|
|
92
|
+
|
|
93
|
+
// The generic gate supersedes the legacy membersOf-specific gate: when
|
|
94
|
+
// jql-function-arg-hydration is on, any function with arguments is included (covering
|
|
95
|
+
// membersOf and all others). Otherwise fall back to the legacy membersOf-only path.
|
|
96
|
+
var shouldIncludeFunction = (0, _platformFeatureFlags.fg)('jql-function-arg-hydration') ? functionOperand.arguments.length > 0 : functionName === 'membersof' && (0, _platformFeatureFlags.fg)('jira-membersof-team-support');
|
|
97
|
+
return shouldIncludeFunction ? "".concat(functionOperand.function.text, "(").concat(args, ")") : '';
|
|
98
98
|
});
|
|
99
99
|
return _this;
|
|
100
100
|
}
|
package/dist/cjs/state/index.js
CHANGED
|
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.useStoreActions = exports.useScopedId = exports.useRichInlineNodesEnabled = exports.useOnSyntaxHelp = exports.useLineNumbersVisible = exports.useJqlError = exports.useIsSearching = exports.useIntl = exports.useIdPrefix = exports.useHydratedValue = exports.useHydratedUser = exports.useHydratedTeam = exports.useHydratedProject = exports.useHydratedGoal = exports.useHydratedDeprecations = exports.useExternalMessages = exports.useEditorViewHasFocus = exports.useEditorView = exports.useEditorStateHasJqlError = exports.useEditorState = exports.useCustomErrorComponent = exports.useAutocompleteProvider = exports.useAutocompletePosition = exports.useAutocompleteOptions = exports.useAutocompleteLoading = exports.useAutocompleteIsOpen = exports.useAutocomplete = exports.initialState = exports.actions = exports.EditorStateContainer = void 0;
|
|
7
|
+
exports.useStoreActions = exports.useScopedId = exports.useRichInlineNodesEnabled = exports.useOnSyntaxHelp = exports.useLineNumbersVisible = exports.useJqlError = exports.useIsSearching = exports.useIntl = exports.useIdPrefix = exports.useHydratedValue = exports.useHydratedUser = exports.useHydratedTeam = exports.useHydratedProject = exports.useHydratedLozengeWithAvatar = exports.useHydratedGoal = exports.useHydratedDeprecations = exports.useExternalMessages = exports.useEditorViewHasFocus = exports.useEditorView = exports.useEditorStateHasJqlError = exports.useEditorState = exports.useCustomErrorComponent = exports.useAutocompleteProvider = exports.useAutocompletePosition = exports.useAutocompleteOptions = exports.useAutocompleteLoading = exports.useAutocompleteIsOpen = exports.useAutocomplete = exports.initialState = exports.actions = exports.EditorStateContainer = void 0;
|
|
8
8
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
9
9
|
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
10
10
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
@@ -950,16 +950,25 @@ var useHydratedGoal = exports.useHydratedGoal = (0, _reactSweetState.createHook)
|
|
|
950
950
|
return goal && goal.type === 'goal' ? goal : undefined;
|
|
951
951
|
}
|
|
952
952
|
});
|
|
953
|
+
var useHydratedLozengeWithAvatar = exports.useHydratedLozengeWithAvatar = (0, _reactSweetState.createHook)(Store, {
|
|
954
|
+
selector: function selector(state, _ref43) {
|
|
955
|
+
var _state$hydratedValues0;
|
|
956
|
+
var id = _ref43.id,
|
|
957
|
+
fieldName = _ref43.fieldName;
|
|
958
|
+
var value = (_state$hydratedValues0 = state.hydratedValues[(0, _util.normaliseHydrationKey)(fieldName)]) === null || _state$hydratedValues0 === void 0 ? void 0 : _state$hydratedValues0.get((0, _jqlAst.normaliseJqlString)(id));
|
|
959
|
+
return value && value.type === 'lozengeWithAvatar' ? value : undefined;
|
|
960
|
+
}
|
|
961
|
+
});
|
|
953
962
|
var useHydratedDeprecations = exports.useHydratedDeprecations = (0, _reactSweetState.createHook)(Store, {
|
|
954
963
|
selector: function selector(state) {
|
|
955
964
|
var ast = (0, _jqlAst2.getJastFromState)(state.editorState);
|
|
956
965
|
var fieldsInQuery = (0, _util2.getFieldNodes)(ast);
|
|
957
966
|
var toReturn = [];
|
|
958
|
-
Object.entries(state.hydratedValues).forEach(function (
|
|
959
|
-
var _state$
|
|
960
|
-
var
|
|
961
|
-
fieldName =
|
|
962
|
-
(_state$
|
|
967
|
+
Object.entries(state.hydratedValues).forEach(function (_ref44) {
|
|
968
|
+
var _state$hydratedValues1;
|
|
969
|
+
var _ref45 = (0, _slicedToArray2.default)(_ref44, 1),
|
|
970
|
+
fieldName = _ref45[0];
|
|
971
|
+
(_state$hydratedValues1 = state.hydratedValues[fieldName]) === null || _state$hydratedValues1 === void 0 || _state$hydratedValues1.forEach(function (value) {
|
|
963
972
|
if (value.type === 'deprecated-field') {
|
|
964
973
|
if (fieldsInQuery.has(value.id.toLowerCase())) {
|
|
965
974
|
toReturn.push(value);
|
|
@@ -982,19 +991,19 @@ var useOnSyntaxHelp = exports.useOnSyntaxHelp = (0, _reactSweetState.createHook)
|
|
|
982
991
|
});
|
|
983
992
|
var EditorStateContainer = exports.EditorStateContainer = (0, _reactSweetState.createContainer)(Store, {
|
|
984
993
|
onInit: function onInit() {
|
|
985
|
-
return function (
|
|
986
|
-
var getState =
|
|
987
|
-
setState =
|
|
988
|
-
dispatch =
|
|
989
|
-
var intlRef =
|
|
990
|
-
query =
|
|
991
|
-
isSearching =
|
|
992
|
-
autocompleteProvider =
|
|
993
|
-
externalMessages =
|
|
994
|
-
enableRichInlineNodes =
|
|
995
|
-
onDebugUnsafeMessage =
|
|
996
|
-
onSyntaxHelp =
|
|
997
|
-
customComponents =
|
|
994
|
+
return function (_ref46, _ref47) {
|
|
995
|
+
var getState = _ref46.getState,
|
|
996
|
+
setState = _ref46.setState,
|
|
997
|
+
dispatch = _ref46.dispatch;
|
|
998
|
+
var intlRef = _ref47.intlRef,
|
|
999
|
+
query = _ref47.query,
|
|
1000
|
+
isSearching = _ref47.isSearching,
|
|
1001
|
+
autocompleteProvider = _ref47.autocompleteProvider,
|
|
1002
|
+
externalMessages = _ref47.externalMessages,
|
|
1003
|
+
enableRichInlineNodes = _ref47.enableRichInlineNodes,
|
|
1004
|
+
onDebugUnsafeMessage = _ref47.onDebugUnsafeMessage,
|
|
1005
|
+
onSyntaxHelp = _ref47.onSyntaxHelp,
|
|
1006
|
+
customComponents = _ref47.customComponents;
|
|
998
1007
|
setState({
|
|
999
1008
|
controlledQuery: query,
|
|
1000
1009
|
query: query,
|
|
@@ -1023,18 +1032,18 @@ var EditorStateContainer = exports.EditorStateContainer = (0, _reactSweetState.c
|
|
|
1023
1032
|
};
|
|
1024
1033
|
},
|
|
1025
1034
|
onUpdate: function onUpdate() {
|
|
1026
|
-
return function (
|
|
1027
|
-
var getState =
|
|
1028
|
-
setState =
|
|
1029
|
-
dispatch =
|
|
1030
|
-
var controlledQueryProp =
|
|
1031
|
-
isSearching =
|
|
1032
|
-
autocompleteProvider =
|
|
1033
|
-
externalMessages =
|
|
1034
|
-
enableRichInlineNodes =
|
|
1035
|
-
onDebugUnsafeMessage =
|
|
1036
|
-
onSyntaxHelp =
|
|
1037
|
-
customComponents =
|
|
1035
|
+
return function (_ref48, _ref49) {
|
|
1036
|
+
var getState = _ref48.getState,
|
|
1037
|
+
setState = _ref48.setState,
|
|
1038
|
+
dispatch = _ref48.dispatch;
|
|
1039
|
+
var controlledQueryProp = _ref49.query,
|
|
1040
|
+
isSearching = _ref49.isSearching,
|
|
1041
|
+
autocompleteProvider = _ref49.autocompleteProvider,
|
|
1042
|
+
externalMessages = _ref49.externalMessages,
|
|
1043
|
+
enableRichInlineNodes = _ref49.enableRichInlineNodes,
|
|
1044
|
+
onDebugUnsafeMessage = _ref49.onDebugUnsafeMessage,
|
|
1045
|
+
onSyntaxHelp = _ref49.onSyntaxHelp,
|
|
1046
|
+
customComponents = _ref49.customComponents;
|
|
1038
1047
|
var _getState12 = getState(),
|
|
1039
1048
|
controlledQuery = _getState12.controlledQuery,
|
|
1040
1049
|
query = _getState12.query;
|
|
@@ -33,7 +33,7 @@ _styled.default.div({
|
|
|
33
33
|
},
|
|
34
34
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
|
|
35
35
|
'&[disabled]': {
|
|
36
|
-
background: "var(--ds-background-disabled, #
|
|
36
|
+
background: "var(--ds-background-disabled, #0515240F)"
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
});
|
|
@@ -97,7 +97,7 @@ function (props) {
|
|
|
97
97
|
var ReadOnlyEditorViewContainer = exports.ReadOnlyEditorViewContainer =
|
|
98
98
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-styled, @atlaskit/ui-styling-standard/no-exported-styles -- Ignored via go/DSP-18766
|
|
99
99
|
(0, _styled.default)(EditorViewContainer)({
|
|
100
|
-
backgroundColor: "var(--ds-background-disabled, #
|
|
100
|
+
backgroundColor: "var(--ds-background-disabled, #0515240F)",
|
|
101
101
|
color: "var(--ds-text-disabled, #080F214A)",
|
|
102
102
|
pointerEvents: 'none'
|
|
103
103
|
});
|
|
@@ -10,7 +10,6 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
|
|
|
10
10
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
11
11
|
var _react = _interopRequireWildcard(require("react"));
|
|
12
12
|
var _throttle = _interopRequireDefault(require("lodash/throttle"));
|
|
13
|
-
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
14
13
|
var _constants = require("../../common/constants");
|
|
15
14
|
var _useEditorTheme = require("../../hooks/use-editor-theme");
|
|
16
15
|
var _useEditorViewIsInvalid = require("../../hooks/use-editor-view-is-invalid");
|
|
@@ -129,7 +128,7 @@ var JQLEditorView = function JQLEditorView(_ref) {
|
|
|
129
128
|
'aria-activedescendant': selectedOptionId
|
|
130
129
|
}), editorViewIsInvalid && {
|
|
131
130
|
'aria-invalid': 'true'
|
|
132
|
-
}),
|
|
131
|
+
}), {}, {
|
|
133
132
|
tabindex: '0'
|
|
134
133
|
});
|
|
135
134
|
}, [editorId, isAutocompleteOpen, intl, autocompleteId, editorViewIsInvalid, validationId, helpContentId, selectedOptionId]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { ANALYTICS_CHANNEL, useJqlPackageAnalytics } from '@atlaskit/jql-editor-common';
|
|
2
2
|
export const useJqlEditorAnalytics = analyticsSource => {
|
|
3
|
-
return useJqlPackageAnalytics(analyticsSource, "@atlaskit/jql-editor", "6.4.
|
|
3
|
+
return useJqlPackageAnalytics(analyticsSource, "@atlaskit/jql-editor", "6.4.7", ANALYTICS_CHANNEL);
|
|
4
4
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { goal } from './goal';
|
|
2
|
+
import { lozengeWithAvatar } from './lozenge-with-avatar';
|
|
2
3
|
import { project } from './project';
|
|
3
4
|
import { team } from './team';
|
|
4
5
|
import { user } from './user';
|
|
5
6
|
export const richInlineNodes = {
|
|
7
|
+
lozengeWithAvatar,
|
|
6
8
|
user,
|
|
7
9
|
team,
|
|
8
10
|
project,
|