@atlaskit/editor-plugin-text-formatting 30.1.0 → 30.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @atlaskit/editor-plugin-text-formatting
2
2
 
3
+ ## 30.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`54f4bfbdf94ad`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/54f4bfbdf94ad) -
8
+ [ux] Allow text styles on selections that contain inline code, behind
9
+ platform_editor_style_around_inline_code
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+
3
15
  ## 30.1.0
4
16
 
5
17
  ### Minor Changes
@@ -6,7 +6,19 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.toggleUnderlineWithAnalytics = exports.toggleUnderline = exports.toggleSuperscriptWithAnalytics = exports.toggleSuperscript = exports.toggleSubscriptWithAnalytics = exports.toggleSubscript = exports.toggleStrongWithAnalytics = exports.toggleStrong = exports.toggleStrikeWithAnalytics = exports.toggleStrike = exports.toggleEmWithAnalytics = exports.toggleEm = exports.toggleCodeWithAnalytics = exports.toggleCode = void 0;
7
7
  var _enums = require("@atlaskit/editor-common/analytics/types/enums");
8
8
  var _commands = require("@atlaskit/editor-common/mark/commands");
9
+ var _fg = require("@atlaskit/platform-feature-flags/fg");
9
10
  var _marks = require("./utils/marks");
11
+ /**
12
+ * Toggles a text style mark. Behind `platform_editor_style_around_inline_code` it uses the
13
+ * package's own toggle, which can remove a style from a selection that also contains text
14
+ * excluding it (such as inline code).
15
+ */
16
+ var toggleStyleMark = function toggleStyleMark(markType, attrs) {
17
+ if ((0, _fg.fg)('platform_editor_style_around_inline_code')) {
18
+ return (0, _marks.nextToggleMark)(markType, undefined, attrs);
19
+ }
20
+ return (0, _commands.toggleMark)(markType, attrs);
21
+ };
10
22
  var toggleEm = exports.toggleEm = function toggleEm(_ref) {
11
23
  var tr = _ref.tr;
12
24
  var em = tr.doc.type.schema.marks.em;
@@ -14,7 +26,7 @@ var toggleEm = exports.toggleEm = function toggleEm(_ref) {
14
26
  // No transaction to apply
15
27
  return null;
16
28
  }
17
- return (0, _commands.toggleMark)(em)({
29
+ return toggleStyleMark(em)({
18
30
  tr: tr
19
31
  });
20
32
  };
@@ -49,7 +61,7 @@ var toggleStrike = exports.toggleStrike = function toggleStrike(_ref3) {
49
61
  // No transaction to apply
50
62
  return null;
51
63
  }
52
- return (0, _commands.toggleMark)(strike)({
64
+ return toggleStyleMark(strike)({
53
65
  tr: tr
54
66
  });
55
67
  };
@@ -84,7 +96,7 @@ var toggleStrong = exports.toggleStrong = function toggleStrong(_ref5) {
84
96
  // No transaction to apply
85
97
  return null;
86
98
  }
87
- return (0, _commands.toggleMark)(strong)({
99
+ return toggleStyleMark(strong)({
88
100
  tr: tr
89
101
  });
90
102
  };
@@ -119,7 +131,7 @@ var toggleUnderline = exports.toggleUnderline = function toggleUnderline(_ref7)
119
131
  // No transaction to apply
120
132
  return null;
121
133
  }
122
- return (0, _commands.toggleMark)(underline)({
134
+ return toggleStyleMark(underline)({
123
135
  tr: tr
124
136
  });
125
137
  };
@@ -154,7 +166,7 @@ var toggleSuperscript = exports.toggleSuperscript = function toggleSuperscript(_
154
166
  // No transaction to apply
155
167
  return null;
156
168
  }
157
- return (0, _commands.toggleMark)(subsup, {
169
+ return toggleStyleMark(subsup, {
158
170
  type: 'sup'
159
171
  })({
160
172
  tr: tr
@@ -191,7 +203,7 @@ var toggleSubscript = exports.toggleSubscript = function toggleSubscript(_ref1)
191
203
  // No transaction to apply
192
204
  return null;
193
205
  }
194
- return (0, _commands.toggleMark)(subsup, {
206
+ return toggleStyleMark(subsup, {
195
207
  type: 'sub'
196
208
  })({
197
209
  tr: tr
@@ -33,6 +33,33 @@ var nextToggleMark = exports.nextToggleMark = function nextToggleMark(markType,
33
33
  });
34
34
  };
35
35
  };
36
+
37
+ /**
38
+ * Like `entireSelectionContainsMark`, but ignores text that can never carry the mark because
39
+ * one of its marks excludes it (for example inline code excludes bold). Without this, a mixed
40
+ * selection containing inline code could be styled but never un-styled.
41
+ *
42
+ * Returns true when no text in the range can take the mark (for example a selection of only
43
+ * inline code). The caller then removes a mark that is not there, which changes nothing.
44
+ */
45
+ var entireStyleableSelectionContainsMark = function entireStyleableSelectionContainsMark(mark, doc, fromPos, toPos) {
46
+ var onlyContainsMark = true;
47
+ doc.nodesBetween(fromPos, toPos, function (node) {
48
+ if (!onlyContainsMark) {
49
+ return false;
50
+ }
51
+ if (node.isText && !(0, _mark.isMarkExcluded)(mark.type, node.marks)) {
52
+ onlyContainsMark = mark.isInSet(node.marks);
53
+ }
54
+ });
55
+ return onlyContainsMark;
56
+ };
57
+ var selectionContainsMark = function selectionContainsMark(mark, doc, fromPos, toPos) {
58
+ if ((0, _fg.fg)('platform_editor_style_around_inline_code')) {
59
+ return entireStyleableSelectionContainsMark(mark, doc, fromPos, toPos);
60
+ }
61
+ return (0, _mark.entireSelectionContainsMark)(mark, doc, fromPos, toPos);
62
+ };
36
63
  var nextToggleMarkInRange = function nextToggleMarkInRange(mark, api) {
37
64
  return function (_ref2) {
38
65
  var tr = _ref2.tr;
@@ -46,7 +73,7 @@ var nextToggleMarkInRange = function nextToggleMarkInRange(mark, api) {
46
73
  });
47
74
  var from = cellPos;
48
75
  var to = cellPos + cell.nodeSize;
49
- removeMark && (removeMark = (0, _mark.entireSelectionContainsMark)(mark, tr.doc, from, to));
76
+ removeMark && (removeMark = selectionContainsMark(mark, tr.doc, from, to));
50
77
  });
51
78
  for (var i = cells.length - 1; i >= 0; i--) {
52
79
  var cell = cells[i];
@@ -63,7 +90,7 @@ var nextToggleMarkInRange = function nextToggleMarkInRange(mark, api) {
63
90
  // Scenario 1: Selection contains both bold and non-bold text -> bold entire selection
64
91
  // Scenario 2: Selection contains only bold text -> un-bold entire selection
65
92
  // Scenario 3: Selection contains no bold text -> bold entire selection
66
- var _removeMark = (0, _mark.entireSelectionContainsMark)(mark, tr.doc, $from.pos, $to.pos);
93
+ var _removeMark = selectionContainsMark(mark, tr.doc, $from.pos, $to.pos);
67
94
  nextApplyMarkOnRange($from.pos, $to.pos, _removeMark, mark, tr, api);
68
95
  }
69
96
  if (tr.docChanged) {
@@ -15,6 +15,7 @@ var _utils = require("@atlaskit/editor-common/utils");
15
15
  var _commands = require("@atlaskit/editor-prosemirror/commands");
16
16
  var _model = require("@atlaskit/editor-prosemirror/model");
17
17
  var _state = require("@atlaskit/editor-prosemirror/state");
18
+ var _fg = require("@atlaskit/platform-feature-flags/fg");
18
19
  var _editorExperiment = require("@atlaskit/tmp-editor-statsig/editor-experiment");
19
20
  var _textFormatting2 = _interopRequireWildcard(require("../editor-commands/text-formatting"));
20
21
  var commands = _textFormatting2;
@@ -41,6 +42,59 @@ var checkNodeSelection = function checkNodeSelection(mark, editorState, type) {
41
42
  type: type
42
43
  })(editorState);
43
44
  };
45
+
46
+ /**
47
+ * Whether a range selection holds any text that can take `markType`, i.e. text whose marks
48
+ * (such as inline code) do not exclude it. Cursor selections return false.
49
+ */
50
+ var selectionHasTextAcceptingMark = function selectionHasTextAcceptingMark(editorState, markType) {
51
+ var selection = editorState.selection,
52
+ doc = editorState.doc;
53
+ if (selection.empty) {
54
+ return false;
55
+ }
56
+ var hasTextAcceptingMark = false;
57
+ var _iterator = _createForOfIteratorHelper(selection.ranges),
58
+ _step;
59
+ try {
60
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
61
+ var _step$value = _step.value,
62
+ $from = _step$value.$from,
63
+ $to = _step$value.$to;
64
+ doc.nodesBetween($from.pos, $to.pos, function (node) {
65
+ if (hasTextAcceptingMark) {
66
+ return false;
67
+ }
68
+ if (node.isText && !(0, _mark.isMarkExcluded)(markType, node.marks)) {
69
+ hasTextAcceptingMark = true;
70
+ }
71
+ });
72
+ if (hasTextAcceptingMark) {
73
+ break;
74
+ }
75
+ }
76
+ } catch (err) {
77
+ _iterator.e(err);
78
+ } finally {
79
+ _iterator.f();
80
+ }
81
+ return hasTextAcceptingMark;
82
+ };
83
+
84
+ /**
85
+ * Whether inline code in the selection should disable `markType`. Behind
86
+ * `platform_editor_style_around_inline_code`, code only disables a mark when no other text in the
87
+ * selection could take it.
88
+ */
89
+ var isDisabledByCode = function isDisabledByCode(codeInSelection, markType, editorState) {
90
+ if (!codeInSelection) {
91
+ return false;
92
+ }
93
+ if ((0, _fg.fg)('platform_editor_style_around_inline_code')) {
94
+ return !selectionHasTextAcceptingMark(editorState, markType);
95
+ }
96
+ return true;
97
+ };
44
98
  var getTextFormattingState = function getTextFormattingState(editorState, _editorAnalyticsAPI) {
45
99
  var _editorState$schema$m = editorState.schema.marks,
46
100
  em = _editorState$schema$m.em,
@@ -74,26 +128,27 @@ var getTextFormattingState = function getTextFormattingState(editorState, _edito
74
128
  });
75
129
  var marksToName = new Map(marks);
76
130
  var activeMarks = (0, _textFormatting.wholeSelectionHasMarks)(editorState, Array.from(marksToName.keys()));
77
- var _iterator = _createForOfIteratorHelper(marks),
78
- _step;
131
+ var _iterator2 = _createForOfIteratorHelper(marks),
132
+ _step2;
79
133
  try {
80
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
81
- var _step$value = (0, _slicedToArray2.default)(_step.value, 2),
82
- mark = _step$value[0],
83
- markName = _step$value[1];
134
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
135
+ var _step2$value = (0, _slicedToArray2.default)(_step2.value, 2),
136
+ mark = _step2$value[0],
137
+ markName = _step2$value[1];
84
138
  var active = activeMarks.get(mark);
85
139
  if (active !== undefined) {
86
140
  state["".concat(markName, "Active")] = active;
87
141
  }
142
+ var markType = mark instanceof _model.MarkType ? mark : mark.type;
88
143
  state["".concat(markName, "Disabled")] =
89
144
  // Disable when code is active, except for code itself which should not be disabled
90
145
  // when code is in selection 😅
91
- state.codeInSelection && markName !== 'code' ? true : !checkNodeSelection(mark instanceof _model.MarkType ? mark : mark.type, editorState);
146
+ markName !== 'code' && isDisabledByCode(state.codeInSelection, markType, editorState) ? true : !checkNodeSelection(markType, editorState);
92
147
  }
93
148
  } catch (err) {
94
- _iterator.e(err);
149
+ _iterator2.e(err);
95
150
  } finally {
96
- _iterator.f();
151
+ _iterator2.f();
97
152
  }
98
153
  return state;
99
154
  }
@@ -103,15 +158,15 @@ var getTextFormattingState = function getTextFormattingState(editorState, _edito
103
158
  }
104
159
  if (em) {
105
160
  state.emActive = (0, _mark.anyMarkActive)(editorState, em);
106
- state.emDisabled = state.codeActive ? true : !checkNodeSelection(em, editorState);
161
+ state.emDisabled = isDisabledByCode(state.codeActive, em, editorState) ? true : !checkNodeSelection(em, editorState);
107
162
  }
108
163
  if (strike) {
109
164
  state.strikeActive = (0, _mark.anyMarkActive)(editorState, strike);
110
- state.strikeDisabled = state.codeActive ? true : !checkNodeSelection(strike, editorState);
165
+ state.strikeDisabled = isDisabledByCode(state.codeActive, strike, editorState) ? true : !checkNodeSelection(strike, editorState);
111
166
  }
112
167
  if (strong) {
113
168
  state.strongActive = (0, _mark.anyMarkActive)(editorState, strong);
114
- state.strongDisabled = state.codeActive ? true : !checkNodeSelection(strong, editorState);
169
+ state.strongDisabled = isDisabledByCode(state.codeActive, strong, editorState) ? true : !checkNodeSelection(strong, editorState);
115
170
  }
116
171
  if (subsup) {
117
172
  var subMark = subsup.create({
@@ -122,12 +177,12 @@ var getTextFormattingState = function getTextFormattingState(editorState, _edito
122
177
  });
123
178
  state.subscriptActive = (0, _mark.anyMarkActive)(editorState, subMark);
124
179
  state.superscriptActive = (0, _mark.anyMarkActive)(editorState, supMark);
125
- state.subscriptDisabled = state.codeActive ? true : !checkNodeSelection(subsup, editorState, 'sub');
126
- state.superscriptDisabled = state.codeActive ? true : !checkNodeSelection(subsup, editorState, 'sup');
180
+ state.subscriptDisabled = isDisabledByCode(state.codeActive, subsup, editorState) ? true : !checkNodeSelection(subsup, editorState, 'sub');
181
+ state.superscriptDisabled = isDisabledByCode(state.codeActive, subsup, editorState) ? true : !checkNodeSelection(subsup, editorState, 'sup');
127
182
  }
128
183
  if (underline) {
129
184
  state.underlineActive = (0, _mark.anyMarkActive)(editorState, underline);
130
- state.underlineDisabled = state.codeActive ? true : !checkNodeSelection(underline, editorState);
185
+ state.underlineDisabled = isDisabledByCode(state.codeActive, underline, editorState) ? true : !checkNodeSelection(underline, editorState);
131
186
  }
132
187
  return state;
133
188
  };
@@ -1,6 +1,19 @@
1
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics/types/enums';
2
2
  import { toggleMark } from '@atlaskit/editor-common/mark/commands';
3
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
3
4
  import { nextToggleMark } from './utils/marks';
5
+
6
+ /**
7
+ * Toggles a text style mark. Behind `platform_editor_style_around_inline_code` it uses the
8
+ * package's own toggle, which can remove a style from a selection that also contains text
9
+ * excluding it (such as inline code).
10
+ */
11
+ const toggleStyleMark = (markType, attrs) => {
12
+ if (fg('platform_editor_style_around_inline_code')) {
13
+ return nextToggleMark(markType, undefined, attrs);
14
+ }
15
+ return toggleMark(markType, attrs);
16
+ };
4
17
  export const toggleEm = ({
5
18
  tr
6
19
  }) => {
@@ -11,7 +24,7 @@ export const toggleEm = ({
11
24
  // No transaction to apply
12
25
  return null;
13
26
  }
14
- return toggleMark(em)({
27
+ return toggleStyleMark(em)({
15
28
  tr
16
29
  });
17
30
  };
@@ -46,7 +59,7 @@ export const toggleStrike = ({
46
59
  // No transaction to apply
47
60
  return null;
48
61
  }
49
- return toggleMark(strike)({
62
+ return toggleStyleMark(strike)({
50
63
  tr
51
64
  });
52
65
  };
@@ -81,7 +94,7 @@ export const toggleStrong = ({
81
94
  // No transaction to apply
82
95
  return null;
83
96
  }
84
- return toggleMark(strong)({
97
+ return toggleStyleMark(strong)({
85
98
  tr
86
99
  });
87
100
  };
@@ -116,7 +129,7 @@ export const toggleUnderline = ({
116
129
  // No transaction to apply
117
130
  return null;
118
131
  }
119
- return toggleMark(underline)({
132
+ return toggleStyleMark(underline)({
120
133
  tr
121
134
  });
122
135
  };
@@ -151,7 +164,7 @@ export const toggleSuperscript = ({
151
164
  // No transaction to apply
152
165
  return null;
153
166
  }
154
- return toggleMark(subsup, {
167
+ return toggleStyleMark(subsup, {
155
168
  type: 'sup'
156
169
  })({
157
170
  tr
@@ -188,7 +201,7 @@ export const toggleSubscript = ({
188
201
  // No transaction to apply
189
202
  return null;
190
203
  }
191
- return toggleMark(subsup, {
204
+ return toggleStyleMark(subsup, {
192
205
  type: 'sub'
193
206
  })({
194
207
  tr
@@ -1,4 +1,4 @@
1
- import { entireSelectionContainsMark } from '@atlaskit/editor-common/mark';
1
+ import { entireSelectionContainsMark, isMarkExcluded } from '@atlaskit/editor-common/mark';
2
2
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
3
3
  import { CellSelection } from '@atlaskit/editor-tables/cell-selection';
4
4
  import { fg } from '@atlaskit/platform-feature-flags/fg';
@@ -26,6 +26,33 @@ export const nextToggleMark = (markType, api, attrs) => ({
26
26
  tr
27
27
  });
28
28
  };
29
+
30
+ /**
31
+ * Like `entireSelectionContainsMark`, but ignores text that can never carry the mark because
32
+ * one of its marks excludes it (for example inline code excludes bold). Without this, a mixed
33
+ * selection containing inline code could be styled but never un-styled.
34
+ *
35
+ * Returns true when no text in the range can take the mark (for example a selection of only
36
+ * inline code). The caller then removes a mark that is not there, which changes nothing.
37
+ */
38
+ const entireStyleableSelectionContainsMark = (mark, doc, fromPos, toPos) => {
39
+ let onlyContainsMark = true;
40
+ doc.nodesBetween(fromPos, toPos, node => {
41
+ if (!onlyContainsMark) {
42
+ return false;
43
+ }
44
+ if (node.isText && !isMarkExcluded(mark.type, node.marks)) {
45
+ onlyContainsMark = mark.isInSet(node.marks);
46
+ }
47
+ });
48
+ return onlyContainsMark;
49
+ };
50
+ const selectionContainsMark = (mark, doc, fromPos, toPos) => {
51
+ if (fg('platform_editor_style_around_inline_code')) {
52
+ return entireStyleableSelectionContainsMark(mark, doc, fromPos, toPos);
53
+ }
54
+ return entireSelectionContainsMark(mark, doc, fromPos, toPos);
55
+ };
29
56
  const nextToggleMarkInRange = (mark, api) => ({
30
57
  tr
31
58
  }) => {
@@ -39,7 +66,7 @@ const nextToggleMarkInRange = (mark, api) => ({
39
66
  });
40
67
  const from = cellPos;
41
68
  const to = cellPos + cell.nodeSize;
42
- removeMark && (removeMark = entireSelectionContainsMark(mark, tr.doc, from, to));
69
+ removeMark && (removeMark = selectionContainsMark(mark, tr.doc, from, to));
43
70
  });
44
71
  for (let i = cells.length - 1; i >= 0; i--) {
45
72
  const cell = cells[i];
@@ -57,7 +84,7 @@ const nextToggleMarkInRange = (mark, api) => ({
57
84
  // Scenario 1: Selection contains both bold and non-bold text -> bold entire selection
58
85
  // Scenario 2: Selection contains only bold text -> un-bold entire selection
59
86
  // Scenario 3: Selection contains no bold text -> bold entire selection
60
- const removeMark = entireSelectionContainsMark(mark, tr.doc, $from.pos, $to.pos);
87
+ const removeMark = selectionContainsMark(mark, tr.doc, $from.pos, $to.pos);
61
88
  nextApplyMarkOnRange($from.pos, $to.pos, removeMark, mark, tr, api);
62
89
  }
63
90
  if (tr.docChanged) {
@@ -2,13 +2,14 @@
2
2
  // import { toggleMark } from '@atlaskit/editor-common/mark';
3
3
 
4
4
  import { moveLeft as keymapMoveLeft, moveRight as keymapMoveRight } from '@atlaskit/editor-common/keymaps';
5
- import { anyMarkActive } from '@atlaskit/editor-common/mark';
5
+ import { anyMarkActive, isMarkExcluded } from '@atlaskit/editor-common/mark';
6
6
  import { wholeSelectionHasMarks } from '@atlaskit/editor-common/mark/text-formatting';
7
7
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
8
8
  import { shallowEqual } from '@atlaskit/editor-common/utils';
9
9
  import { toggleMark } from '@atlaskit/editor-prosemirror/commands';
10
10
  import { MarkType } from '@atlaskit/editor-prosemirror/model';
11
11
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
12
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
12
13
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/editor-experiment';
13
14
  import { createInlineCodeFromTextInputWithAnalytics } from '../editor-commands/text-formatting';
14
15
  // Ignored via go/ees005
@@ -30,6 +31,53 @@ const checkNodeSelection = (mark, editorState, type) => {
30
31
  type: type
31
32
  })(editorState);
32
33
  };
34
+
35
+ /**
36
+ * Whether a range selection holds any text that can take `markType`, i.e. text whose marks
37
+ * (such as inline code) do not exclude it. Cursor selections return false.
38
+ */
39
+ const selectionHasTextAcceptingMark = (editorState, markType) => {
40
+ const {
41
+ selection,
42
+ doc
43
+ } = editorState;
44
+ if (selection.empty) {
45
+ return false;
46
+ }
47
+ let hasTextAcceptingMark = false;
48
+ for (const {
49
+ $from,
50
+ $to
51
+ } of selection.ranges) {
52
+ doc.nodesBetween($from.pos, $to.pos, node => {
53
+ if (hasTextAcceptingMark) {
54
+ return false;
55
+ }
56
+ if (node.isText && !isMarkExcluded(markType, node.marks)) {
57
+ hasTextAcceptingMark = true;
58
+ }
59
+ });
60
+ if (hasTextAcceptingMark) {
61
+ break;
62
+ }
63
+ }
64
+ return hasTextAcceptingMark;
65
+ };
66
+
67
+ /**
68
+ * Whether inline code in the selection should disable `markType`. Behind
69
+ * `platform_editor_style_around_inline_code`, code only disables a mark when no other text in the
70
+ * selection could take it.
71
+ */
72
+ const isDisabledByCode = (codeInSelection, markType, editorState) => {
73
+ if (!codeInSelection) {
74
+ return false;
75
+ }
76
+ if (fg('platform_editor_style_around_inline_code')) {
77
+ return !selectionHasTextAcceptingMark(editorState, markType);
78
+ }
79
+ return true;
80
+ };
33
81
  const getTextFormattingState = (editorState, _editorAnalyticsAPI) => {
34
82
  const {
35
83
  em,
@@ -65,10 +113,11 @@ const getTextFormattingState = (editorState, _editorAnalyticsAPI) => {
65
113
  if (active !== undefined) {
66
114
  state[`${markName}Active`] = active;
67
115
  }
116
+ const markType = mark instanceof MarkType ? mark : mark.type;
68
117
  state[`${markName}Disabled`] =
69
118
  // Disable when code is active, except for code itself which should not be disabled
70
119
  // when code is in selection 😅
71
- state.codeInSelection && markName !== 'code' ? true : !checkNodeSelection(mark instanceof MarkType ? mark : mark.type, editorState);
120
+ markName !== 'code' && isDisabledByCode(state.codeInSelection, markType, editorState) ? true : !checkNodeSelection(markType, editorState);
72
121
  }
73
122
  return state;
74
123
  }
@@ -78,15 +127,15 @@ const getTextFormattingState = (editorState, _editorAnalyticsAPI) => {
78
127
  }
79
128
  if (em) {
80
129
  state.emActive = anyMarkActive(editorState, em);
81
- state.emDisabled = state.codeActive ? true : !checkNodeSelection(em, editorState);
130
+ state.emDisabled = isDisabledByCode(state.codeActive, em, editorState) ? true : !checkNodeSelection(em, editorState);
82
131
  }
83
132
  if (strike) {
84
133
  state.strikeActive = anyMarkActive(editorState, strike);
85
- state.strikeDisabled = state.codeActive ? true : !checkNodeSelection(strike, editorState);
134
+ state.strikeDisabled = isDisabledByCode(state.codeActive, strike, editorState) ? true : !checkNodeSelection(strike, editorState);
86
135
  }
87
136
  if (strong) {
88
137
  state.strongActive = anyMarkActive(editorState, strong);
89
- state.strongDisabled = state.codeActive ? true : !checkNodeSelection(strong, editorState);
138
+ state.strongDisabled = isDisabledByCode(state.codeActive, strong, editorState) ? true : !checkNodeSelection(strong, editorState);
90
139
  }
91
140
  if (subsup) {
92
141
  const subMark = subsup.create({
@@ -97,12 +146,12 @@ const getTextFormattingState = (editorState, _editorAnalyticsAPI) => {
97
146
  });
98
147
  state.subscriptActive = anyMarkActive(editorState, subMark);
99
148
  state.superscriptActive = anyMarkActive(editorState, supMark);
100
- state.subscriptDisabled = state.codeActive ? true : !checkNodeSelection(subsup, editorState, 'sub');
101
- state.superscriptDisabled = state.codeActive ? true : !checkNodeSelection(subsup, editorState, 'sup');
149
+ state.subscriptDisabled = isDisabledByCode(state.codeActive, subsup, editorState) ? true : !checkNodeSelection(subsup, editorState, 'sub');
150
+ state.superscriptDisabled = isDisabledByCode(state.codeActive, subsup, editorState) ? true : !checkNodeSelection(subsup, editorState, 'sup');
102
151
  }
103
152
  if (underline) {
104
153
  state.underlineActive = anyMarkActive(editorState, underline);
105
- state.underlineDisabled = state.codeActive ? true : !checkNodeSelection(underline, editorState);
154
+ state.underlineDisabled = isDisabledByCode(state.codeActive, underline, editorState) ? true : !checkNodeSelection(underline, editorState);
106
155
  }
107
156
  return state;
108
157
  };
@@ -1,6 +1,19 @@
1
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics/types/enums';
2
2
  import { toggleMark } from '@atlaskit/editor-common/mark/commands';
3
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
3
4
  import { nextToggleMark } from './utils/marks';
5
+
6
+ /**
7
+ * Toggles a text style mark. Behind `platform_editor_style_around_inline_code` it uses the
8
+ * package's own toggle, which can remove a style from a selection that also contains text
9
+ * excluding it (such as inline code).
10
+ */
11
+ var toggleStyleMark = function toggleStyleMark(markType, attrs) {
12
+ if (fg('platform_editor_style_around_inline_code')) {
13
+ return nextToggleMark(markType, undefined, attrs);
14
+ }
15
+ return toggleMark(markType, attrs);
16
+ };
4
17
  export var toggleEm = function toggleEm(_ref) {
5
18
  var tr = _ref.tr;
6
19
  var em = tr.doc.type.schema.marks.em;
@@ -8,7 +21,7 @@ export var toggleEm = function toggleEm(_ref) {
8
21
  // No transaction to apply
9
22
  return null;
10
23
  }
11
- return toggleMark(em)({
24
+ return toggleStyleMark(em)({
12
25
  tr: tr
13
26
  });
14
27
  };
@@ -43,7 +56,7 @@ export var toggleStrike = function toggleStrike(_ref3) {
43
56
  // No transaction to apply
44
57
  return null;
45
58
  }
46
- return toggleMark(strike)({
59
+ return toggleStyleMark(strike)({
47
60
  tr: tr
48
61
  });
49
62
  };
@@ -78,7 +91,7 @@ export var toggleStrong = function toggleStrong(_ref5) {
78
91
  // No transaction to apply
79
92
  return null;
80
93
  }
81
- return toggleMark(strong)({
94
+ return toggleStyleMark(strong)({
82
95
  tr: tr
83
96
  });
84
97
  };
@@ -113,7 +126,7 @@ export var toggleUnderline = function toggleUnderline(_ref7) {
113
126
  // No transaction to apply
114
127
  return null;
115
128
  }
116
- return toggleMark(underline)({
129
+ return toggleStyleMark(underline)({
117
130
  tr: tr
118
131
  });
119
132
  };
@@ -148,7 +161,7 @@ export var toggleSuperscript = function toggleSuperscript(_ref9) {
148
161
  // No transaction to apply
149
162
  return null;
150
163
  }
151
- return toggleMark(subsup, {
164
+ return toggleStyleMark(subsup, {
152
165
  type: 'sup'
153
166
  })({
154
167
  tr: tr
@@ -185,7 +198,7 @@ export var toggleSubscript = function toggleSubscript(_ref1) {
185
198
  // No transaction to apply
186
199
  return null;
187
200
  }
188
- return toggleMark(subsup, {
201
+ return toggleStyleMark(subsup, {
189
202
  type: 'sub'
190
203
  })({
191
204
  tr: tr
@@ -1,4 +1,4 @@
1
- import { entireSelectionContainsMark } from '@atlaskit/editor-common/mark';
1
+ import { entireSelectionContainsMark, isMarkExcluded } from '@atlaskit/editor-common/mark';
2
2
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
3
3
  import { CellSelection } from '@atlaskit/editor-tables/cell-selection';
4
4
  import { fg } from '@atlaskit/platform-feature-flags/fg';
@@ -27,6 +27,33 @@ export var nextToggleMark = function nextToggleMark(markType, api, attrs) {
27
27
  });
28
28
  };
29
29
  };
30
+
31
+ /**
32
+ * Like `entireSelectionContainsMark`, but ignores text that can never carry the mark because
33
+ * one of its marks excludes it (for example inline code excludes bold). Without this, a mixed
34
+ * selection containing inline code could be styled but never un-styled.
35
+ *
36
+ * Returns true when no text in the range can take the mark (for example a selection of only
37
+ * inline code). The caller then removes a mark that is not there, which changes nothing.
38
+ */
39
+ var entireStyleableSelectionContainsMark = function entireStyleableSelectionContainsMark(mark, doc, fromPos, toPos) {
40
+ var onlyContainsMark = true;
41
+ doc.nodesBetween(fromPos, toPos, function (node) {
42
+ if (!onlyContainsMark) {
43
+ return false;
44
+ }
45
+ if (node.isText && !isMarkExcluded(mark.type, node.marks)) {
46
+ onlyContainsMark = mark.isInSet(node.marks);
47
+ }
48
+ });
49
+ return onlyContainsMark;
50
+ };
51
+ var selectionContainsMark = function selectionContainsMark(mark, doc, fromPos, toPos) {
52
+ if (fg('platform_editor_style_around_inline_code')) {
53
+ return entireStyleableSelectionContainsMark(mark, doc, fromPos, toPos);
54
+ }
55
+ return entireSelectionContainsMark(mark, doc, fromPos, toPos);
56
+ };
30
57
  var nextToggleMarkInRange = function nextToggleMarkInRange(mark, api) {
31
58
  return function (_ref2) {
32
59
  var tr = _ref2.tr;
@@ -40,7 +67,7 @@ var nextToggleMarkInRange = function nextToggleMarkInRange(mark, api) {
40
67
  });
41
68
  var from = cellPos;
42
69
  var to = cellPos + cell.nodeSize;
43
- removeMark && (removeMark = entireSelectionContainsMark(mark, tr.doc, from, to));
70
+ removeMark && (removeMark = selectionContainsMark(mark, tr.doc, from, to));
44
71
  });
45
72
  for (var i = cells.length - 1; i >= 0; i--) {
46
73
  var cell = cells[i];
@@ -57,7 +84,7 @@ var nextToggleMarkInRange = function nextToggleMarkInRange(mark, api) {
57
84
  // Scenario 1: Selection contains both bold and non-bold text -> bold entire selection
58
85
  // Scenario 2: Selection contains only bold text -> un-bold entire selection
59
86
  // Scenario 3: Selection contains no bold text -> bold entire selection
60
- var _removeMark = entireSelectionContainsMark(mark, tr.doc, $from.pos, $to.pos);
87
+ var _removeMark = selectionContainsMark(mark, tr.doc, $from.pos, $to.pos);
61
88
  nextApplyMarkOnRange($from.pos, $to.pos, _removeMark, mark, tr, api);
62
89
  }
63
90
  if (tr.docChanged) {
@@ -6,13 +6,14 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
6
6
  // import { toggleMark } from '@atlaskit/editor-common/mark';
7
7
 
8
8
  import { moveLeft as keymapMoveLeft, moveRight as keymapMoveRight } from '@atlaskit/editor-common/keymaps';
9
- import { anyMarkActive } from '@atlaskit/editor-common/mark';
9
+ import { anyMarkActive, isMarkExcluded } from '@atlaskit/editor-common/mark';
10
10
  import { wholeSelectionHasMarks } from '@atlaskit/editor-common/mark/text-formatting';
11
11
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
12
12
  import { shallowEqual } from '@atlaskit/editor-common/utils';
13
13
  import { toggleMark } from '@atlaskit/editor-prosemirror/commands';
14
14
  import { MarkType } from '@atlaskit/editor-prosemirror/model';
15
15
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
16
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
16
17
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/editor-experiment';
17
18
  import { createInlineCodeFromTextInputWithAnalytics } from '../editor-commands/text-formatting';
18
19
  // Ignored via go/ees005
@@ -34,6 +35,59 @@ var checkNodeSelection = function checkNodeSelection(mark, editorState, type) {
34
35
  type: type
35
36
  })(editorState);
36
37
  };
38
+
39
+ /**
40
+ * Whether a range selection holds any text that can take `markType`, i.e. text whose marks
41
+ * (such as inline code) do not exclude it. Cursor selections return false.
42
+ */
43
+ var selectionHasTextAcceptingMark = function selectionHasTextAcceptingMark(editorState, markType) {
44
+ var selection = editorState.selection,
45
+ doc = editorState.doc;
46
+ if (selection.empty) {
47
+ return false;
48
+ }
49
+ var hasTextAcceptingMark = false;
50
+ var _iterator = _createForOfIteratorHelper(selection.ranges),
51
+ _step;
52
+ try {
53
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
54
+ var _step$value = _step.value,
55
+ $from = _step$value.$from,
56
+ $to = _step$value.$to;
57
+ doc.nodesBetween($from.pos, $to.pos, function (node) {
58
+ if (hasTextAcceptingMark) {
59
+ return false;
60
+ }
61
+ if (node.isText && !isMarkExcluded(markType, node.marks)) {
62
+ hasTextAcceptingMark = true;
63
+ }
64
+ });
65
+ if (hasTextAcceptingMark) {
66
+ break;
67
+ }
68
+ }
69
+ } catch (err) {
70
+ _iterator.e(err);
71
+ } finally {
72
+ _iterator.f();
73
+ }
74
+ return hasTextAcceptingMark;
75
+ };
76
+
77
+ /**
78
+ * Whether inline code in the selection should disable `markType`. Behind
79
+ * `platform_editor_style_around_inline_code`, code only disables a mark when no other text in the
80
+ * selection could take it.
81
+ */
82
+ var isDisabledByCode = function isDisabledByCode(codeInSelection, markType, editorState) {
83
+ if (!codeInSelection) {
84
+ return false;
85
+ }
86
+ if (fg('platform_editor_style_around_inline_code')) {
87
+ return !selectionHasTextAcceptingMark(editorState, markType);
88
+ }
89
+ return true;
90
+ };
37
91
  var getTextFormattingState = function getTextFormattingState(editorState, _editorAnalyticsAPI) {
38
92
  var _editorState$schema$m = editorState.schema.marks,
39
93
  em = _editorState$schema$m.em,
@@ -67,26 +121,27 @@ var getTextFormattingState = function getTextFormattingState(editorState, _edito
67
121
  });
68
122
  var marksToName = new Map(marks);
69
123
  var activeMarks = wholeSelectionHasMarks(editorState, Array.from(marksToName.keys()));
70
- var _iterator = _createForOfIteratorHelper(marks),
71
- _step;
124
+ var _iterator2 = _createForOfIteratorHelper(marks),
125
+ _step2;
72
126
  try {
73
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
74
- var _step$value = _slicedToArray(_step.value, 2),
75
- mark = _step$value[0],
76
- markName = _step$value[1];
127
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
128
+ var _step2$value = _slicedToArray(_step2.value, 2),
129
+ mark = _step2$value[0],
130
+ markName = _step2$value[1];
77
131
  var active = activeMarks.get(mark);
78
132
  if (active !== undefined) {
79
133
  state["".concat(markName, "Active")] = active;
80
134
  }
135
+ var markType = mark instanceof MarkType ? mark : mark.type;
81
136
  state["".concat(markName, "Disabled")] =
82
137
  // Disable when code is active, except for code itself which should not be disabled
83
138
  // when code is in selection 😅
84
- state.codeInSelection && markName !== 'code' ? true : !checkNodeSelection(mark instanceof MarkType ? mark : mark.type, editorState);
139
+ markName !== 'code' && isDisabledByCode(state.codeInSelection, markType, editorState) ? true : !checkNodeSelection(markType, editorState);
85
140
  }
86
141
  } catch (err) {
87
- _iterator.e(err);
142
+ _iterator2.e(err);
88
143
  } finally {
89
- _iterator.f();
144
+ _iterator2.f();
90
145
  }
91
146
  return state;
92
147
  }
@@ -96,15 +151,15 @@ var getTextFormattingState = function getTextFormattingState(editorState, _edito
96
151
  }
97
152
  if (em) {
98
153
  state.emActive = anyMarkActive(editorState, em);
99
- state.emDisabled = state.codeActive ? true : !checkNodeSelection(em, editorState);
154
+ state.emDisabled = isDisabledByCode(state.codeActive, em, editorState) ? true : !checkNodeSelection(em, editorState);
100
155
  }
101
156
  if (strike) {
102
157
  state.strikeActive = anyMarkActive(editorState, strike);
103
- state.strikeDisabled = state.codeActive ? true : !checkNodeSelection(strike, editorState);
158
+ state.strikeDisabled = isDisabledByCode(state.codeActive, strike, editorState) ? true : !checkNodeSelection(strike, editorState);
104
159
  }
105
160
  if (strong) {
106
161
  state.strongActive = anyMarkActive(editorState, strong);
107
- state.strongDisabled = state.codeActive ? true : !checkNodeSelection(strong, editorState);
162
+ state.strongDisabled = isDisabledByCode(state.codeActive, strong, editorState) ? true : !checkNodeSelection(strong, editorState);
108
163
  }
109
164
  if (subsup) {
110
165
  var subMark = subsup.create({
@@ -115,12 +170,12 @@ var getTextFormattingState = function getTextFormattingState(editorState, _edito
115
170
  });
116
171
  state.subscriptActive = anyMarkActive(editorState, subMark);
117
172
  state.superscriptActive = anyMarkActive(editorState, supMark);
118
- state.subscriptDisabled = state.codeActive ? true : !checkNodeSelection(subsup, editorState, 'sub');
119
- state.superscriptDisabled = state.codeActive ? true : !checkNodeSelection(subsup, editorState, 'sup');
173
+ state.subscriptDisabled = isDisabledByCode(state.codeActive, subsup, editorState) ? true : !checkNodeSelection(subsup, editorState, 'sub');
174
+ state.superscriptDisabled = isDisabledByCode(state.codeActive, subsup, editorState) ? true : !checkNodeSelection(subsup, editorState, 'sup');
120
175
  }
121
176
  if (underline) {
122
177
  state.underlineActive = anyMarkActive(editorState, underline);
123
- state.underlineDisabled = state.codeActive ? true : !checkNodeSelection(underline, editorState);
178
+ state.underlineDisabled = isDisabledByCode(state.codeActive, underline, editorState) ? true : !checkNodeSelection(underline, editorState);
124
179
  }
125
180
  return state;
126
181
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-text-formatting",
3
- "version": "30.1.0",
3
+ "version": "30.2.0",
4
4
  "description": "Text-formatting plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -31,7 +31,7 @@
31
31
  "@atlaskit/editor-plugin-selection-toolbar": "^31.0.0",
32
32
  "@atlaskit/editor-plugin-toolbar": "^27.0.0",
33
33
  "@atlaskit/editor-prosemirror": "^9.0.0",
34
- "@atlaskit/editor-shared-styles": "^4.6.0",
34
+ "@atlaskit/editor-shared-styles": "^5.0.0",
35
35
  "@atlaskit/editor-tables": "^3.2.0",
36
36
  "@atlaskit/editor-toolbar": "^2.8.0",
37
37
  "@atlaskit/editor-toolbar-model": "^1.2.0",
@@ -45,7 +45,7 @@
45
45
  "@emotion/react": "^11.7.1"
46
46
  },
47
47
  "peerDependencies": {
48
- "@atlaskit/editor-common": "^134.3.0",
48
+ "@atlaskit/editor-common": "^134.4.0",
49
49
  "react": "^18.2.0 || ^19.2.0",
50
50
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
51
51
  },
@@ -56,6 +56,9 @@
56
56
  "platform-feature-flags": {
57
57
  "editor_inline_comments_on_inline_nodes": {
58
58
  "type": "boolean"
59
+ },
60
+ "platform_editor_style_around_inline_code": {
61
+ "type": "boolean"
59
62
  }
60
63
  }
61
64
  }