@atlaskit/editor-plugin-block-type 14.1.7 → 14.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,16 @@
1
1
  # @atlaskit/editor-plugin-block-type
2
2
 
3
+ ## 14.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`1f36a5ab4e9ae`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/1f36a5ab4e9ae) -
8
+ Support markdown source view heading and quote toolbar actions
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies
13
+
3
14
  ## 14.1.7
4
15
 
5
16
  ### Patch Changes
@@ -13,12 +13,15 @@ var React = _interopRequireWildcard(require("react"));
13
13
  var _runtime = require("@compiled/react/runtime");
14
14
  var _reactIntl = require("react-intl");
15
15
  var _analytics = require("@atlaskit/editor-common/analytics");
16
+ var _hooks = require("@atlaskit/editor-common/hooks");
16
17
  var _keymaps = require("@atlaskit/editor-common/keymaps");
17
18
  var _toolbar = require("@atlaskit/editor-common/toolbar");
18
19
  var _ugcTokens = require("@atlaskit/editor-common/ugc-tokens");
19
20
  var _useSharedPluginStateSelector = require("@atlaskit/editor-common/use-shared-plugin-state-selector");
20
21
  var _editorToolbar = require("@atlaskit/editor-toolbar");
22
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
21
23
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
24
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
22
25
  var _utils = require("../../utils");
23
26
  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); }
24
27
  var smallTextStyle = null;
@@ -115,6 +118,16 @@ var HeadingButton = exports.HeadingButton = function HeadingButton(_ref2) {
115
118
  formatMessage = _useIntl.formatMessage;
116
119
  var currentBlockType = (0, _useSharedPluginStateSelector.useSharedPluginStateSelector)(api, 'blockType.currentBlockType');
117
120
  var availableBlockTypesInDropdown = (0, _useSharedPluginStateSelector.useSharedPluginStateSelector)(api, 'blockType.availableBlockTypesInDropdown');
121
+ var isMarkdownBridgeEnabled = (0, _expValEqualsNoExposure.expValEqualsNoExposure)('cc-markdown-mode', 'isEnabled', true) && (0, _platformFeatureFlags.fg)('platform_editor_markdown_compatible_toolbar');
122
+ var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['markdownMode'], function (states) {
123
+ var _states$markdownModeS, _states$markdownModeS2;
124
+ return {
125
+ markdownView: isMarkdownBridgeEnabled ? (_states$markdownModeS = states.markdownModeState) === null || _states$markdownModeS === void 0 ? void 0 : _states$markdownModeS.view : undefined,
126
+ sourceFormatState: isMarkdownBridgeEnabled ? (_states$markdownModeS2 = states.markdownModeState) === null || _states$markdownModeS2 === void 0 ? void 0 : _states$markdownModeS2.sourceBlockFormatState : null
127
+ };
128
+ }),
129
+ markdownView = _useSharedPluginState.markdownView,
130
+ sourceFormatState = _useSharedPluginState.sourceFormatState;
118
131
  var _useEditorToolbar = (0, _toolbar.useEditorToolbar)(),
119
132
  editorView = _useEditorToolbar.editorView;
120
133
  if (!(availableBlockTypesInDropdown !== null && availableBlockTypesInDropdown !== void 0 && availableBlockTypesInDropdown.some(function (availableBlockType) {
@@ -122,17 +135,59 @@ var HeadingButton = exports.HeadingButton = function HeadingButton(_ref2) {
122
135
  }))) {
123
136
  return null;
124
137
  }
125
- var isDisabled = (0, _expValEquals.expValEquals)('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableHeadingButton(editorView === null || editorView === void 0 ? void 0 : editorView.state, blockType) : false;
138
+
139
+ // When in source view and the bridge is enabled, read heading level from
140
+ // the CM6 Lezer-tree-derived format state rather than the PM block type state.
141
+ // markdownView is authoritative for "in source view" — it flips synchronously
142
+ // on setView. sourceFormatState is null between the view switch and the
143
+ // first CM6 update listener fire, so we can't rely on it as the sentinel.
144
+ var isMarkdownBridgeActive = markdownView === 'syntax';
145
+
146
+ // Extract the numeric level from the block type name (e.g. 'heading1' → 1).
147
+ var headingLevel = blockType.name.startsWith('heading') ? parseInt(blockType.name.replace('heading', ''), 10) : null;
148
+ var isDisabled = isMarkdownBridgeActive ? Boolean(sourceFormatState === null || sourceFormatState === void 0 ? void 0 : sourceFormatState.inCodeBlock) : (0, _expValEquals.expValEquals)('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableHeadingButton(editorView === null || editorView === void 0 ? void 0 : editorView.state, blockType) : false;
126
149
  var fromBlockQuote = (currentBlockType === null || currentBlockType === void 0 ? void 0 : currentBlockType.name) === 'blockquote';
127
150
  var onClick = function onClick() {
128
151
  var _api$core, _api$blockType;
129
152
  if (isDisabled) {
130
153
  return;
131
154
  }
155
+
156
+ // Route to CM6 when in source view.
157
+ //
158
+ // - heading1..6: apply/replace via toggleSourceHeading(targetLevel).
159
+ // - normal (paragraph): if the cursor is currently on a heading line,
160
+ // call toggleSourceHeading with the *current* level — toggleHeading
161
+ // removes the prefix when invoked with the matching level, so this
162
+ // downgrades the heading back to a plain paragraph. No-op when not
163
+ // on a heading (already normal) or when smallText is selected (no
164
+ // markdown equivalent).
165
+ if (isMarkdownBridgeActive) {
166
+ var _sourceFormatState$he;
167
+ var targetLevel = blockType.name === 'normal' ? (_sourceFormatState$he = sourceFormatState === null || sourceFormatState === void 0 ? void 0 : sourceFormatState.headingLevel) !== null && _sourceFormatState$he !== void 0 ? _sourceFormatState$he : null : headingLevel;
168
+ if (targetLevel !== null && targetLevel >= 1 && targetLevel <= 6) {
169
+ var _api$markdownMode;
170
+ api === null || api === void 0 || (_api$markdownMode = api.markdownMode) === null || _api$markdownMode === void 0 || _api$markdownMode.actions.toggleSourceHeading(targetLevel);
171
+ }
172
+ return;
173
+ }
132
174
  api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 || (_api$blockType = api.blockType) === null || _api$blockType === void 0 || (_api$blockType = _api$blockType.commands) === null || _api$blockType === void 0 ? void 0 : _api$blockType.setTextLevel(blockType.name, _analytics.INPUT_METHOD.TOOLBAR, fromBlockQuote));
133
175
  };
134
176
  var shortcut = (0, _keymaps.formatShortcut)(shortcuts[blockType.name]);
135
- var isSelected = (currentBlockType === null || currentBlockType === void 0 ? void 0 : currentBlockType.name) === blockType.name;
177
+
178
+ // In source view, derive isSelected from the Lezer heading level.
179
+ // In WYSIWYG, use the existing PM block type comparison.
180
+ //
181
+ // Branches in source view:
182
+ // - heading1..6: select when the source heading level matches.
183
+ // - normal (paragraph): select when the source has no heading and is not
184
+ // inside a blockquote — i.e. cursor is on a plain markdown paragraph.
185
+ // - smallText: no markdown equivalent, so never selected in source view.
186
+ //
187
+ // Guard on `sourceFormatState != null` up-front so the body can read the
188
+ // fields directly — clearer than nested optional chains and not fragile to
189
+ // future reordering of the conditions.
190
+ var isSelected = isMarkdownBridgeActive ? sourceFormatState != null ? headingLevel !== null ? sourceFormatState.headingLevel === headingLevel : blockType.name === 'normal' ? sourceFormatState.headingLevel === null && !sourceFormatState.inBlockquote : false : false : (currentBlockType === null || currentBlockType === void 0 ? void 0 : currentBlockType.name) === blockType.name;
136
191
  return /*#__PURE__*/React.createElement(_editorToolbar.ToolbarDropdownItem, {
137
192
  elemBefore: blockType.icon,
138
193
  elemAfter: shortcut ? /*#__PURE__*/React.createElement(_editorToolbar.ToolbarKeyboardShortcutHint, {
@@ -8,11 +8,14 @@ exports.QuoteButton = void 0;
8
8
  var _react = _interopRequireDefault(require("react"));
9
9
  var _reactIntl = require("react-intl");
10
10
  var _analytics = require("@atlaskit/editor-common/analytics");
11
+ var _hooks = require("@atlaskit/editor-common/hooks");
11
12
  var _keymaps = require("@atlaskit/editor-common/keymaps");
12
13
  var _toolbar = require("@atlaskit/editor-common/toolbar");
13
14
  var _useSharedPluginStateSelector = require("@atlaskit/editor-common/use-shared-plugin-state-selector");
14
15
  var _editorToolbar = require("@atlaskit/editor-toolbar");
16
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
15
17
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
18
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
16
19
  var _utils = require("../../utils");
17
20
  var shouldDisableQuoteButton = function shouldDisableQuoteButton(state) {
18
21
  if (!state) {
@@ -29,27 +32,48 @@ var QuoteButton = exports.QuoteButton = function QuoteButton(_ref) {
29
32
  var currentBlockType = (0, _useSharedPluginStateSelector.useSharedPluginStateSelector)(api, 'blockType.currentBlockType');
30
33
  var _useEditorToolbar = (0, _toolbar.useEditorToolbar)(),
31
34
  editorView = _useEditorToolbar.editorView;
35
+ var isMarkdownBridgeEnabled = (0, _expValEqualsNoExposure.expValEqualsNoExposure)('cc-markdown-mode', 'isEnabled', true) && (0, _platformFeatureFlags.fg)('platform_editor_markdown_compatible_toolbar');
36
+ var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['markdownMode'], function (states) {
37
+ var _states$markdownModeS, _states$markdownModeS2;
38
+ return {
39
+ markdownView: isMarkdownBridgeEnabled ? (_states$markdownModeS = states.markdownModeState) === null || _states$markdownModeS === void 0 ? void 0 : _states$markdownModeS.view : undefined,
40
+ sourceBlockFormatState: isMarkdownBridgeEnabled ? (_states$markdownModeS2 = states.markdownModeState) === null || _states$markdownModeS2 === void 0 ? void 0 : _states$markdownModeS2.sourceBlockFormatState : null
41
+ };
42
+ }),
43
+ markdownView = _useSharedPluginState.markdownView,
44
+ sourceBlockFormatState = _useSharedPluginState.sourceBlockFormatState;
32
45
  if (!(availableBlockTypesInDropdown !== null && availableBlockTypesInDropdown !== void 0 && availableBlockTypesInDropdown.some(function (availableBlockType) {
33
46
  return availableBlockType.name === blockType.name;
34
47
  }))) {
35
48
  return null;
36
49
  }
37
- var isDisabled = (0, _expValEquals.expValEquals)('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableQuoteButton(editorView === null || editorView === void 0 ? void 0 : editorView.state) : false;
50
+
51
+ // markdownView is authoritative for "in source view" — it flips synchronously
52
+ // on setView. sourceBlockFormatState is null between the view switch and the
53
+ // first CM6 update listener fire, so we can't rely on it as the sentinel.
54
+ var isMarkdownBridgeActive = markdownView === 'syntax';
55
+ var isDisabled = isMarkdownBridgeActive ? Boolean(sourceBlockFormatState === null || sourceBlockFormatState === void 0 ? void 0 : sourceBlockFormatState.inCodeBlock) : (0, _expValEquals.expValEquals)('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableQuoteButton(editorView === null || editorView === void 0 ? void 0 : editorView.state) : false;
38
56
  var onClick = function onClick() {
39
57
  var _api$core, _api$blockType;
40
58
  if (isDisabled) {
41
59
  return;
42
60
  }
61
+ if (isMarkdownBridgeActive) {
62
+ var _api$markdownMode;
63
+ api === null || api === void 0 || (_api$markdownMode = api.markdownMode) === null || _api$markdownMode === void 0 || _api$markdownMode.actions.toggleSourceBlockquote();
64
+ return;
65
+ }
43
66
  api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 || (_api$blockType = api.blockType) === null || _api$blockType === void 0 || (_api$blockType = _api$blockType.commands) === null || _api$blockType === void 0 ? void 0 : _api$blockType.insertBlockQuote(_analytics.INPUT_METHOD.TOOLBAR));
44
67
  };
45
68
  var shortcut = (0, _keymaps.formatShortcut)(_keymaps.toggleBlockQuote);
69
+ var isSelected = isMarkdownBridgeActive ? Boolean(sourceBlockFormatState === null || sourceBlockFormatState === void 0 ? void 0 : sourceBlockFormatState.inBlockquote) : currentBlockType === blockType;
46
70
  return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItem, {
47
71
  elemBefore: blockType.icon,
48
72
  elemAfter: shortcut ? /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarKeyboardShortcutHint, {
49
73
  shortcut: shortcut
50
74
  }) : undefined,
51
75
  onClick: onClick,
52
- isSelected: currentBlockType === blockType,
76
+ isSelected: isSelected,
53
77
  isDisabled: isDisabled,
54
78
  ariaKeyshortcuts: shortcut
55
79
  }, formatMessage(blockType.title));
@@ -11,17 +11,56 @@ var _hooks = require("@atlaskit/editor-common/hooks");
11
11
  var _messages = require("@atlaskit/editor-common/messages");
12
12
  var _toolbar = require("@atlaskit/editor-common/toolbar");
13
13
  var _editorToolbar = require("@atlaskit/editor-toolbar");
14
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
14
15
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
16
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
15
17
  var _blockTypes = require("../../block-types");
16
18
  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); }
17
19
  var usePluginState = function usePluginState(api) {
18
- return (0, _hooks.useSharedPluginStateWithSelector)(api, ['blockType', 'interaction'], function (state) {
19
- var _state$blockTypeState, _state$interactionSta, _state$blockTypeState2;
20
+ // Memoized inside the hook (rather than at module scope) so the gate read
21
+ // inside `toolbarBlockTypesWithRank` doesn't run at module import time —
22
+ // that breaks downstream tests that import preset-default without
23
+ // initializing the FeatureGates client.
24
+ var sourceViewBlockTypeValues = (0, _react.useMemo)(function () {
25
+ return Object.values((0, _blockTypes.toolbarBlockTypesWithRank)({}));
26
+ }, []);
27
+ var selectPluginState = (0, _react.useCallback)(function (state) {
28
+ var _state$interactionSta, _state$blockTypeState, _state$markdownModeSt, _state$markdownModeSt2, _sourceViewBlockTypeV, _sourceViewBlockTypeV2, _sourceViewBlockTypeV3, _state$blockTypeState2;
29
+ var pmCurrentBlockType = ((_state$interactionSta = state.interactionState) === null || _state$interactionSta === void 0 ? void 0 : _state$interactionSta.interactionState) === 'hasNotHadInteraction' && (0, _expValEquals.expValEquals)('platform_editor_default_toolbar_state', 'isEnabled', true) ? undefined : (_state$blockTypeState = state.blockTypeState) === null || _state$blockTypeState === void 0 ? void 0 : _state$blockTypeState.currentBlockType;
30
+
31
+ // When the markdown bridge is active and the source view is mounted,
32
+ // derive currentBlockType from the CM6 Lezer heading level rather than
33
+ // PM block type state (which reflects the last WYSIWYG cursor position).
34
+ var isMarkdownBridgeEnabled = (0, _expValEqualsNoExposure.expValEqualsNoExposure)('cc-markdown-mode', 'isEnabled', true) && (0, _platformFeatureFlags.fg)('platform_editor_markdown_compatible_toolbar');
35
+ var sourceFormatState = isMarkdownBridgeEnabled ? (_state$markdownModeSt = state.markdownModeState) === null || _state$markdownModeSt === void 0 ? void 0 : _state$markdownModeSt.sourceBlockFormatState : null;
36
+ // markdownView is authoritative for "in source view" — it flips synchronously
37
+ // on setView. sourceFormatState is null between the view switch and the first
38
+ // CM6 update listener fire, so we can't rely on it as the sentinel.
39
+ var isSourceViewActive = isMarkdownBridgeEnabled && ((_state$markdownModeSt2 = state.markdownModeState) === null || _state$markdownModeSt2 === void 0 ? void 0 : _state$markdownModeSt2.view) === 'syntax';
40
+
41
+ // When in source view, derive currentBlockType from CM6 Lezer state
42
+ // (heading level or blockquote) rather than PM block type state.
43
+ // Guard on sourceFormatState being available — during the race window
44
+ // after switching to source view but before CM6 fires its first update,
45
+ // sourceFormatState is null even though isSourceViewActive is true.
46
+ var currentBlockType = isSourceViewActive && sourceFormatState ? sourceFormatState.headingLevel !== null ? // In a heading, look up the full BlockType by name.
47
+ (_sourceViewBlockTypeV = sourceViewBlockTypeValues.find(function (bt) {
48
+ return bt.name === "heading".concat(sourceFormatState.headingLevel);
49
+ })) !== null && _sourceViewBlockTypeV !== void 0 ? _sourceViewBlockTypeV : null : sourceFormatState.inBlockquote ? // In a blockquote, look up the blockquote BlockType.
50
+ (_sourceViewBlockTypeV2 = sourceViewBlockTypeValues.find(function (bt) {
51
+ return bt.name === 'blockquote';
52
+ })) !== null && _sourceViewBlockTypeV2 !== void 0 ? _sourceViewBlockTypeV2 : null : // Plain paragraph — look up the 'normal' BlockType so the trigger
53
+ // shows "Normal text" (matches WYSIWYG and HeadingButton's
54
+ // Normal-in-source-view selection logic).
55
+ (_sourceViewBlockTypeV3 = sourceViewBlockTypeValues.find(function (bt) {
56
+ return bt.name === 'normal';
57
+ })) !== null && _sourceViewBlockTypeV3 !== void 0 ? _sourceViewBlockTypeV3 : null : isSourceViewActive ? null : pmCurrentBlockType;
20
58
  return {
21
- blockTypesDisabled: (_state$blockTypeState = state.blockTypeState) === null || _state$blockTypeState === void 0 ? void 0 : _state$blockTypeState.blockTypesDisabled,
22
- currentBlockType: ((_state$interactionSta = state.interactionState) === null || _state$interactionSta === void 0 ? void 0 : _state$interactionSta.interactionState) === 'hasNotHadInteraction' && (0, _expValEquals.expValEquals)('platform_editor_default_toolbar_state', 'isEnabled', true) ? undefined : (_state$blockTypeState2 = state.blockTypeState) === null || _state$blockTypeState2 === void 0 ? void 0 : _state$blockTypeState2.currentBlockType
59
+ blockTypesDisabled: isSourceViewActive && sourceFormatState ? Boolean(sourceFormatState.inCodeBlock) : isSourceViewActive ? false : (_state$blockTypeState2 = state.blockTypeState) === null || _state$blockTypeState2 === void 0 ? void 0 : _state$blockTypeState2.blockTypesDisabled,
60
+ currentBlockType: currentBlockType
23
61
  };
24
- });
62
+ }, [sourceViewBlockTypeValues]);
63
+ return (0, _hooks.useSharedPluginStateWithSelector)(api, ['blockType', 'interaction', 'markdownMode'], selectPluginState);
25
64
  };
26
65
  var TextStylesMenuButton = exports.TextStylesMenuButton = function TextStylesMenuButton(_ref) {
27
66
  var _Object$values$find;
@@ -6,12 +6,15 @@ import * as React from 'react';
6
6
  import { ax, ix } from "@compiled/react/runtime";
7
7
  import { useIntl } from 'react-intl';
8
8
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
9
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
9
10
  import { formatShortcut, setNormalText, toggleHeading1, toggleHeading2, toggleHeading3, toggleHeading4, toggleHeading5, toggleHeading6, toggleSmallText } from '@atlaskit/editor-common/keymaps';
10
11
  import { useEditorToolbar } from '@atlaskit/editor-common/toolbar';
11
12
  import { editorUGCToken } from '@atlaskit/editor-common/ugc-tokens';
12
13
  import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector';
13
14
  import { ToolbarDropdownItem, ToolbarKeyboardShortcutHint } from '@atlaskit/editor-toolbar';
15
+ import { fg } from '@atlaskit/platform-feature-flags';
14
16
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
17
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
15
18
  import { isSelectionInsideListNode } from '../../utils';
16
19
  const smallTextStyle = null;
17
20
  const normalStyle = null;
@@ -110,23 +113,76 @@ export const HeadingButton = ({
110
113
  } = useIntl();
111
114
  const currentBlockType = useSharedPluginStateSelector(api, 'blockType.currentBlockType');
112
115
  const availableBlockTypesInDropdown = useSharedPluginStateSelector(api, 'blockType.availableBlockTypesInDropdown');
116
+ const isMarkdownBridgeEnabled = expValEqualsNoExposure('cc-markdown-mode', 'isEnabled', true) && fg('platform_editor_markdown_compatible_toolbar');
117
+ const {
118
+ markdownView,
119
+ sourceFormatState
120
+ } = useSharedPluginStateWithSelector(api, ['markdownMode'], states => {
121
+ var _states$markdownModeS, _states$markdownModeS2;
122
+ return {
123
+ markdownView: isMarkdownBridgeEnabled ? (_states$markdownModeS = states.markdownModeState) === null || _states$markdownModeS === void 0 ? void 0 : _states$markdownModeS.view : undefined,
124
+ sourceFormatState: isMarkdownBridgeEnabled ? (_states$markdownModeS2 = states.markdownModeState) === null || _states$markdownModeS2 === void 0 ? void 0 : _states$markdownModeS2.sourceBlockFormatState : null
125
+ };
126
+ });
113
127
  const {
114
128
  editorView
115
129
  } = useEditorToolbar();
116
130
  if (!(availableBlockTypesInDropdown !== null && availableBlockTypesInDropdown !== void 0 && availableBlockTypesInDropdown.some(availableBlockType => availableBlockType.name === blockType.name))) {
117
131
  return null;
118
132
  }
119
- const isDisabled = expValEquals('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableHeadingButton(editorView === null || editorView === void 0 ? void 0 : editorView.state, blockType) : false;
133
+
134
+ // When in source view and the bridge is enabled, read heading level from
135
+ // the CM6 Lezer-tree-derived format state rather than the PM block type state.
136
+ // markdownView is authoritative for "in source view" — it flips synchronously
137
+ // on setView. sourceFormatState is null between the view switch and the
138
+ // first CM6 update listener fire, so we can't rely on it as the sentinel.
139
+ const isMarkdownBridgeActive = markdownView === 'syntax';
140
+
141
+ // Extract the numeric level from the block type name (e.g. 'heading1' → 1).
142
+ const headingLevel = blockType.name.startsWith('heading') ? parseInt(blockType.name.replace('heading', ''), 10) : null;
143
+ const isDisabled = isMarkdownBridgeActive ? Boolean(sourceFormatState === null || sourceFormatState === void 0 ? void 0 : sourceFormatState.inCodeBlock) : expValEquals('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableHeadingButton(editorView === null || editorView === void 0 ? void 0 : editorView.state, blockType) : false;
120
144
  const fromBlockQuote = (currentBlockType === null || currentBlockType === void 0 ? void 0 : currentBlockType.name) === 'blockquote';
121
145
  const onClick = () => {
122
146
  var _api$core, _api$blockType, _api$blockType$comman;
123
147
  if (isDisabled) {
124
148
  return;
125
149
  }
150
+
151
+ // Route to CM6 when in source view.
152
+ //
153
+ // - heading1..6: apply/replace via toggleSourceHeading(targetLevel).
154
+ // - normal (paragraph): if the cursor is currently on a heading line,
155
+ // call toggleSourceHeading with the *current* level — toggleHeading
156
+ // removes the prefix when invoked with the matching level, so this
157
+ // downgrades the heading back to a plain paragraph. No-op when not
158
+ // on a heading (already normal) or when smallText is selected (no
159
+ // markdown equivalent).
160
+ if (isMarkdownBridgeActive) {
161
+ var _sourceFormatState$he;
162
+ const targetLevel = blockType.name === 'normal' ? (_sourceFormatState$he = sourceFormatState === null || sourceFormatState === void 0 ? void 0 : sourceFormatState.headingLevel) !== null && _sourceFormatState$he !== void 0 ? _sourceFormatState$he : null : headingLevel;
163
+ if (targetLevel !== null && targetLevel >= 1 && targetLevel <= 6) {
164
+ var _api$markdownMode;
165
+ api === null || api === void 0 ? void 0 : (_api$markdownMode = api.markdownMode) === null || _api$markdownMode === void 0 ? void 0 : _api$markdownMode.actions.toggleSourceHeading(targetLevel);
166
+ }
167
+ return;
168
+ }
126
169
  api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockType = api.blockType) === null || _api$blockType === void 0 ? void 0 : (_api$blockType$comman = _api$blockType.commands) === null || _api$blockType$comman === void 0 ? void 0 : _api$blockType$comman.setTextLevel(blockType.name, INPUT_METHOD.TOOLBAR, fromBlockQuote));
127
170
  };
128
171
  const shortcut = formatShortcut(shortcuts[blockType.name]);
129
- const isSelected = (currentBlockType === null || currentBlockType === void 0 ? void 0 : currentBlockType.name) === blockType.name;
172
+
173
+ // In source view, derive isSelected from the Lezer heading level.
174
+ // In WYSIWYG, use the existing PM block type comparison.
175
+ //
176
+ // Branches in source view:
177
+ // - heading1..6: select when the source heading level matches.
178
+ // - normal (paragraph): select when the source has no heading and is not
179
+ // inside a blockquote — i.e. cursor is on a plain markdown paragraph.
180
+ // - smallText: no markdown equivalent, so never selected in source view.
181
+ //
182
+ // Guard on `sourceFormatState != null` up-front so the body can read the
183
+ // fields directly — clearer than nested optional chains and not fragile to
184
+ // future reordering of the conditions.
185
+ const isSelected = isMarkdownBridgeActive ? sourceFormatState != null ? headingLevel !== null ? sourceFormatState.headingLevel === headingLevel : blockType.name === 'normal' ? sourceFormatState.headingLevel === null && !sourceFormatState.inBlockquote : false : false : (currentBlockType === null || currentBlockType === void 0 ? void 0 : currentBlockType.name) === blockType.name;
130
186
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
131
187
  elemBefore: blockType.icon,
132
188
  elemAfter: shortcut ? /*#__PURE__*/React.createElement(ToolbarKeyboardShortcutHint, {
@@ -1,11 +1,14 @@
1
1
  import React from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
4
5
  import { formatShortcut, toggleBlockQuote } from '@atlaskit/editor-common/keymaps';
5
6
  import { useEditorToolbar } from '@atlaskit/editor-common/toolbar';
6
7
  import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector';
7
8
  import { ToolbarDropdownItem, ToolbarKeyboardShortcutHint } from '@atlaskit/editor-toolbar';
9
+ import { fg } from '@atlaskit/platform-feature-flags';
8
10
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
11
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
9
12
  import { isSelectionInsideListNode } from '../../utils';
10
13
  const shouldDisableQuoteButton = state => {
11
14
  if (!state) {
@@ -25,25 +28,47 @@ export const QuoteButton = ({
25
28
  const {
26
29
  editorView
27
30
  } = useEditorToolbar();
31
+ const isMarkdownBridgeEnabled = expValEqualsNoExposure('cc-markdown-mode', 'isEnabled', true) && fg('platform_editor_markdown_compatible_toolbar');
32
+ const {
33
+ markdownView,
34
+ sourceBlockFormatState
35
+ } = useSharedPluginStateWithSelector(api, ['markdownMode'], states => {
36
+ var _states$markdownModeS, _states$markdownModeS2;
37
+ return {
38
+ markdownView: isMarkdownBridgeEnabled ? (_states$markdownModeS = states.markdownModeState) === null || _states$markdownModeS === void 0 ? void 0 : _states$markdownModeS.view : undefined,
39
+ sourceBlockFormatState: isMarkdownBridgeEnabled ? (_states$markdownModeS2 = states.markdownModeState) === null || _states$markdownModeS2 === void 0 ? void 0 : _states$markdownModeS2.sourceBlockFormatState : null
40
+ };
41
+ });
28
42
  if (!(availableBlockTypesInDropdown !== null && availableBlockTypesInDropdown !== void 0 && availableBlockTypesInDropdown.some(availableBlockType => availableBlockType.name === blockType.name))) {
29
43
  return null;
30
44
  }
31
- const isDisabled = expValEquals('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableQuoteButton(editorView === null || editorView === void 0 ? void 0 : editorView.state) : false;
45
+
46
+ // markdownView is authoritative for "in source view" — it flips synchronously
47
+ // on setView. sourceBlockFormatState is null between the view switch and the
48
+ // first CM6 update listener fire, so we can't rely on it as the sentinel.
49
+ const isMarkdownBridgeActive = markdownView === 'syntax';
50
+ const isDisabled = isMarkdownBridgeActive ? Boolean(sourceBlockFormatState === null || sourceBlockFormatState === void 0 ? void 0 : sourceBlockFormatState.inCodeBlock) : expValEquals('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableQuoteButton(editorView === null || editorView === void 0 ? void 0 : editorView.state) : false;
32
51
  const onClick = () => {
33
52
  var _api$core, _api$blockType, _api$blockType$comman;
34
53
  if (isDisabled) {
35
54
  return;
36
55
  }
56
+ if (isMarkdownBridgeActive) {
57
+ var _api$markdownMode;
58
+ api === null || api === void 0 ? void 0 : (_api$markdownMode = api.markdownMode) === null || _api$markdownMode === void 0 ? void 0 : _api$markdownMode.actions.toggleSourceBlockquote();
59
+ return;
60
+ }
37
61
  api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockType = api.blockType) === null || _api$blockType === void 0 ? void 0 : (_api$blockType$comman = _api$blockType.commands) === null || _api$blockType$comman === void 0 ? void 0 : _api$blockType$comman.insertBlockQuote(INPUT_METHOD.TOOLBAR));
38
62
  };
39
63
  const shortcut = formatShortcut(toggleBlockQuote);
64
+ const isSelected = isMarkdownBridgeActive ? Boolean(sourceBlockFormatState === null || sourceBlockFormatState === void 0 ? void 0 : sourceBlockFormatState.inBlockquote) : currentBlockType === blockType;
40
65
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
41
66
  elemBefore: blockType.icon,
42
67
  elemAfter: shortcut ? /*#__PURE__*/React.createElement(ToolbarKeyboardShortcutHint, {
43
68
  shortcut: shortcut
44
69
  }) : undefined,
45
70
  onClick: onClick,
46
- isSelected: currentBlockType === blockType,
71
+ isSelected: isSelected,
47
72
  isDisabled: isDisabled,
48
73
  ariaKeyshortcuts: shortcut
49
74
  }, formatMessage(blockType.title));
@@ -1,19 +1,50 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useCallback, useMemo } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
4
4
  import { toolbarMessages } from '@atlaskit/editor-common/messages';
5
5
  import { useEditorToolbar } from '@atlaskit/editor-common/toolbar';
6
6
  import { ToolbarDropdownMenu, ToolbarTooltip, TextIcon } from '@atlaskit/editor-toolbar';
7
+ import { fg } from '@atlaskit/platform-feature-flags';
7
8
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
9
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
8
10
  import { toolbarBlockTypesWithRank } from '../../block-types';
9
11
  const usePluginState = api => {
10
- return useSharedPluginStateWithSelector(api, ['blockType', 'interaction'], state => {
11
- var _state$blockTypeState, _state$interactionSta, _state$blockTypeState2;
12
+ // Memoized inside the hook (rather than at module scope) so the gate read
13
+ // inside `toolbarBlockTypesWithRank` doesn't run at module import time —
14
+ // that breaks downstream tests that import preset-default without
15
+ // initializing the FeatureGates client.
16
+ const sourceViewBlockTypeValues = useMemo(() => Object.values(toolbarBlockTypesWithRank({})), []);
17
+ const selectPluginState = useCallback(state => {
18
+ var _state$interactionSta, _state$blockTypeState, _state$markdownModeSt, _state$markdownModeSt2, _sourceViewBlockTypeV, _sourceViewBlockTypeV2, _sourceViewBlockTypeV3, _state$blockTypeState2;
19
+ const pmCurrentBlockType = ((_state$interactionSta = state.interactionState) === null || _state$interactionSta === void 0 ? void 0 : _state$interactionSta.interactionState) === 'hasNotHadInteraction' && expValEquals('platform_editor_default_toolbar_state', 'isEnabled', true) ? undefined : (_state$blockTypeState = state.blockTypeState) === null || _state$blockTypeState === void 0 ? void 0 : _state$blockTypeState.currentBlockType;
20
+
21
+ // When the markdown bridge is active and the source view is mounted,
22
+ // derive currentBlockType from the CM6 Lezer heading level rather than
23
+ // PM block type state (which reflects the last WYSIWYG cursor position).
24
+ const isMarkdownBridgeEnabled = expValEqualsNoExposure('cc-markdown-mode', 'isEnabled', true) && fg('platform_editor_markdown_compatible_toolbar');
25
+ const sourceFormatState = isMarkdownBridgeEnabled ? (_state$markdownModeSt = state.markdownModeState) === null || _state$markdownModeSt === void 0 ? void 0 : _state$markdownModeSt.sourceBlockFormatState : null;
26
+ // markdownView is authoritative for "in source view" — it flips synchronously
27
+ // on setView. sourceFormatState is null between the view switch and the first
28
+ // CM6 update listener fire, so we can't rely on it as the sentinel.
29
+ const isSourceViewActive = isMarkdownBridgeEnabled && ((_state$markdownModeSt2 = state.markdownModeState) === null || _state$markdownModeSt2 === void 0 ? void 0 : _state$markdownModeSt2.view) === 'syntax';
30
+
31
+ // When in source view, derive currentBlockType from CM6 Lezer state
32
+ // (heading level or blockquote) rather than PM block type state.
33
+ // Guard on sourceFormatState being available — during the race window
34
+ // after switching to source view but before CM6 fires its first update,
35
+ // sourceFormatState is null even though isSourceViewActive is true.
36
+ const currentBlockType = isSourceViewActive && sourceFormatState ? sourceFormatState.headingLevel !== null ? // In a heading, look up the full BlockType by name.
37
+ (_sourceViewBlockTypeV = sourceViewBlockTypeValues.find(bt => bt.name === `heading${sourceFormatState.headingLevel}`)) !== null && _sourceViewBlockTypeV !== void 0 ? _sourceViewBlockTypeV : null : sourceFormatState.inBlockquote ? // In a blockquote, look up the blockquote BlockType.
38
+ (_sourceViewBlockTypeV2 = sourceViewBlockTypeValues.find(bt => bt.name === 'blockquote')) !== null && _sourceViewBlockTypeV2 !== void 0 ? _sourceViewBlockTypeV2 : null : // Plain paragraph — look up the 'normal' BlockType so the trigger
39
+ // shows "Normal text" (matches WYSIWYG and HeadingButton's
40
+ // Normal-in-source-view selection logic).
41
+ (_sourceViewBlockTypeV3 = sourceViewBlockTypeValues.find(bt => bt.name === 'normal')) !== null && _sourceViewBlockTypeV3 !== void 0 ? _sourceViewBlockTypeV3 : null : isSourceViewActive ? null : pmCurrentBlockType;
12
42
  return {
13
- blockTypesDisabled: (_state$blockTypeState = state.blockTypeState) === null || _state$blockTypeState === void 0 ? void 0 : _state$blockTypeState.blockTypesDisabled,
14
- currentBlockType: ((_state$interactionSta = state.interactionState) === null || _state$interactionSta === void 0 ? void 0 : _state$interactionSta.interactionState) === 'hasNotHadInteraction' && expValEquals('platform_editor_default_toolbar_state', 'isEnabled', true) ? undefined : (_state$blockTypeState2 = state.blockTypeState) === null || _state$blockTypeState2 === void 0 ? void 0 : _state$blockTypeState2.currentBlockType
43
+ blockTypesDisabled: isSourceViewActive && sourceFormatState ? Boolean(sourceFormatState.inCodeBlock) : isSourceViewActive ? false : (_state$blockTypeState2 = state.blockTypeState) === null || _state$blockTypeState2 === void 0 ? void 0 : _state$blockTypeState2.blockTypesDisabled,
44
+ currentBlockType
15
45
  };
16
- });
46
+ }, [sourceViewBlockTypeValues]);
47
+ return useSharedPluginStateWithSelector(api, ['blockType', 'interaction', 'markdownMode'], selectPluginState);
17
48
  };
18
49
  export const TextStylesMenuButton = ({
19
50
  allowFontSize,
@@ -6,12 +6,15 @@ import * as React from 'react';
6
6
  import { ax, ix } from "@compiled/react/runtime";
7
7
  import { useIntl } from 'react-intl';
8
8
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
9
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
9
10
  import { formatShortcut, setNormalText, toggleHeading1, toggleHeading2, toggleHeading3, toggleHeading4, toggleHeading5, toggleHeading6, toggleSmallText } from '@atlaskit/editor-common/keymaps';
10
11
  import { useEditorToolbar } from '@atlaskit/editor-common/toolbar';
11
12
  import { editorUGCToken } from '@atlaskit/editor-common/ugc-tokens';
12
13
  import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector';
13
14
  import { ToolbarDropdownItem, ToolbarKeyboardShortcutHint } from '@atlaskit/editor-toolbar';
15
+ import { fg } from '@atlaskit/platform-feature-flags';
14
16
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
17
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
15
18
  import { isSelectionInsideListNode } from '../../utils';
16
19
  var smallTextStyle = null;
17
20
  var normalStyle = null;
@@ -107,6 +110,16 @@ export var HeadingButton = function HeadingButton(_ref2) {
107
110
  formatMessage = _useIntl.formatMessage;
108
111
  var currentBlockType = useSharedPluginStateSelector(api, 'blockType.currentBlockType');
109
112
  var availableBlockTypesInDropdown = useSharedPluginStateSelector(api, 'blockType.availableBlockTypesInDropdown');
113
+ var isMarkdownBridgeEnabled = expValEqualsNoExposure('cc-markdown-mode', 'isEnabled', true) && fg('platform_editor_markdown_compatible_toolbar');
114
+ var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['markdownMode'], function (states) {
115
+ var _states$markdownModeS, _states$markdownModeS2;
116
+ return {
117
+ markdownView: isMarkdownBridgeEnabled ? (_states$markdownModeS = states.markdownModeState) === null || _states$markdownModeS === void 0 ? void 0 : _states$markdownModeS.view : undefined,
118
+ sourceFormatState: isMarkdownBridgeEnabled ? (_states$markdownModeS2 = states.markdownModeState) === null || _states$markdownModeS2 === void 0 ? void 0 : _states$markdownModeS2.sourceBlockFormatState : null
119
+ };
120
+ }),
121
+ markdownView = _useSharedPluginState.markdownView,
122
+ sourceFormatState = _useSharedPluginState.sourceFormatState;
110
123
  var _useEditorToolbar = useEditorToolbar(),
111
124
  editorView = _useEditorToolbar.editorView;
112
125
  if (!(availableBlockTypesInDropdown !== null && availableBlockTypesInDropdown !== void 0 && availableBlockTypesInDropdown.some(function (availableBlockType) {
@@ -114,17 +127,59 @@ export var HeadingButton = function HeadingButton(_ref2) {
114
127
  }))) {
115
128
  return null;
116
129
  }
117
- var isDisabled = expValEquals('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableHeadingButton(editorView === null || editorView === void 0 ? void 0 : editorView.state, blockType) : false;
130
+
131
+ // When in source view and the bridge is enabled, read heading level from
132
+ // the CM6 Lezer-tree-derived format state rather than the PM block type state.
133
+ // markdownView is authoritative for "in source view" — it flips synchronously
134
+ // on setView. sourceFormatState is null between the view switch and the
135
+ // first CM6 update listener fire, so we can't rely on it as the sentinel.
136
+ var isMarkdownBridgeActive = markdownView === 'syntax';
137
+
138
+ // Extract the numeric level from the block type name (e.g. 'heading1' → 1).
139
+ var headingLevel = blockType.name.startsWith('heading') ? parseInt(blockType.name.replace('heading', ''), 10) : null;
140
+ var isDisabled = isMarkdownBridgeActive ? Boolean(sourceFormatState === null || sourceFormatState === void 0 ? void 0 : sourceFormatState.inCodeBlock) : expValEquals('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableHeadingButton(editorView === null || editorView === void 0 ? void 0 : editorView.state, blockType) : false;
118
141
  var fromBlockQuote = (currentBlockType === null || currentBlockType === void 0 ? void 0 : currentBlockType.name) === 'blockquote';
119
142
  var onClick = function onClick() {
120
143
  var _api$core, _api$blockType;
121
144
  if (isDisabled) {
122
145
  return;
123
146
  }
147
+
148
+ // Route to CM6 when in source view.
149
+ //
150
+ // - heading1..6: apply/replace via toggleSourceHeading(targetLevel).
151
+ // - normal (paragraph): if the cursor is currently on a heading line,
152
+ // call toggleSourceHeading with the *current* level — toggleHeading
153
+ // removes the prefix when invoked with the matching level, so this
154
+ // downgrades the heading back to a plain paragraph. No-op when not
155
+ // on a heading (already normal) or when smallText is selected (no
156
+ // markdown equivalent).
157
+ if (isMarkdownBridgeActive) {
158
+ var _sourceFormatState$he;
159
+ var targetLevel = blockType.name === 'normal' ? (_sourceFormatState$he = sourceFormatState === null || sourceFormatState === void 0 ? void 0 : sourceFormatState.headingLevel) !== null && _sourceFormatState$he !== void 0 ? _sourceFormatState$he : null : headingLevel;
160
+ if (targetLevel !== null && targetLevel >= 1 && targetLevel <= 6) {
161
+ var _api$markdownMode;
162
+ api === null || api === void 0 || (_api$markdownMode = api.markdownMode) === null || _api$markdownMode === void 0 || _api$markdownMode.actions.toggleSourceHeading(targetLevel);
163
+ }
164
+ return;
165
+ }
124
166
  api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 || (_api$blockType = api.blockType) === null || _api$blockType === void 0 || (_api$blockType = _api$blockType.commands) === null || _api$blockType === void 0 ? void 0 : _api$blockType.setTextLevel(blockType.name, INPUT_METHOD.TOOLBAR, fromBlockQuote));
125
167
  };
126
168
  var shortcut = formatShortcut(shortcuts[blockType.name]);
127
- var isSelected = (currentBlockType === null || currentBlockType === void 0 ? void 0 : currentBlockType.name) === blockType.name;
169
+
170
+ // In source view, derive isSelected from the Lezer heading level.
171
+ // In WYSIWYG, use the existing PM block type comparison.
172
+ //
173
+ // Branches in source view:
174
+ // - heading1..6: select when the source heading level matches.
175
+ // - normal (paragraph): select when the source has no heading and is not
176
+ // inside a blockquote — i.e. cursor is on a plain markdown paragraph.
177
+ // - smallText: no markdown equivalent, so never selected in source view.
178
+ //
179
+ // Guard on `sourceFormatState != null` up-front so the body can read the
180
+ // fields directly — clearer than nested optional chains and not fragile to
181
+ // future reordering of the conditions.
182
+ var isSelected = isMarkdownBridgeActive ? sourceFormatState != null ? headingLevel !== null ? sourceFormatState.headingLevel === headingLevel : blockType.name === 'normal' ? sourceFormatState.headingLevel === null && !sourceFormatState.inBlockquote : false : false : (currentBlockType === null || currentBlockType === void 0 ? void 0 : currentBlockType.name) === blockType.name;
128
183
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
129
184
  elemBefore: blockType.icon,
130
185
  elemAfter: shortcut ? /*#__PURE__*/React.createElement(ToolbarKeyboardShortcutHint, {
@@ -1,11 +1,14 @@
1
1
  import React from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
4
5
  import { formatShortcut, toggleBlockQuote } from '@atlaskit/editor-common/keymaps';
5
6
  import { useEditorToolbar } from '@atlaskit/editor-common/toolbar';
6
7
  import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector';
7
8
  import { ToolbarDropdownItem, ToolbarKeyboardShortcutHint } from '@atlaskit/editor-toolbar';
9
+ import { fg } from '@atlaskit/platform-feature-flags';
8
10
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
11
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
9
12
  import { isSelectionInsideListNode } from '../../utils';
10
13
  var shouldDisableQuoteButton = function shouldDisableQuoteButton(state) {
11
14
  if (!state) {
@@ -22,27 +25,48 @@ export var QuoteButton = function QuoteButton(_ref) {
22
25
  var currentBlockType = useSharedPluginStateSelector(api, 'blockType.currentBlockType');
23
26
  var _useEditorToolbar = useEditorToolbar(),
24
27
  editorView = _useEditorToolbar.editorView;
28
+ var isMarkdownBridgeEnabled = expValEqualsNoExposure('cc-markdown-mode', 'isEnabled', true) && fg('platform_editor_markdown_compatible_toolbar');
29
+ var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['markdownMode'], function (states) {
30
+ var _states$markdownModeS, _states$markdownModeS2;
31
+ return {
32
+ markdownView: isMarkdownBridgeEnabled ? (_states$markdownModeS = states.markdownModeState) === null || _states$markdownModeS === void 0 ? void 0 : _states$markdownModeS.view : undefined,
33
+ sourceBlockFormatState: isMarkdownBridgeEnabled ? (_states$markdownModeS2 = states.markdownModeState) === null || _states$markdownModeS2 === void 0 ? void 0 : _states$markdownModeS2.sourceBlockFormatState : null
34
+ };
35
+ }),
36
+ markdownView = _useSharedPluginState.markdownView,
37
+ sourceBlockFormatState = _useSharedPluginState.sourceBlockFormatState;
25
38
  if (!(availableBlockTypesInDropdown !== null && availableBlockTypesInDropdown !== void 0 && availableBlockTypesInDropdown.some(function (availableBlockType) {
26
39
  return availableBlockType.name === blockType.name;
27
40
  }))) {
28
41
  return null;
29
42
  }
30
- var isDisabled = expValEquals('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableQuoteButton(editorView === null || editorView === void 0 ? void 0 : editorView.state) : false;
43
+
44
+ // markdownView is authoritative for "in source view" — it flips synchronously
45
+ // on setView. sourceBlockFormatState is null between the view switch and the
46
+ // first CM6 update listener fire, so we can't rely on it as the sentinel.
47
+ var isMarkdownBridgeActive = markdownView === 'syntax';
48
+ var isDisabled = isMarkdownBridgeActive ? Boolean(sourceBlockFormatState === null || sourceBlockFormatState === void 0 ? void 0 : sourceBlockFormatState.inCodeBlock) : expValEquals('platform_editor_small_font_size', 'isEnabled', true) ? shouldDisableQuoteButton(editorView === null || editorView === void 0 ? void 0 : editorView.state) : false;
31
49
  var onClick = function onClick() {
32
50
  var _api$core, _api$blockType;
33
51
  if (isDisabled) {
34
52
  return;
35
53
  }
54
+ if (isMarkdownBridgeActive) {
55
+ var _api$markdownMode;
56
+ api === null || api === void 0 || (_api$markdownMode = api.markdownMode) === null || _api$markdownMode === void 0 || _api$markdownMode.actions.toggleSourceBlockquote();
57
+ return;
58
+ }
36
59
  api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 || (_api$blockType = api.blockType) === null || _api$blockType === void 0 || (_api$blockType = _api$blockType.commands) === null || _api$blockType === void 0 ? void 0 : _api$blockType.insertBlockQuote(INPUT_METHOD.TOOLBAR));
37
60
  };
38
61
  var shortcut = formatShortcut(toggleBlockQuote);
62
+ var isSelected = isMarkdownBridgeActive ? Boolean(sourceBlockFormatState === null || sourceBlockFormatState === void 0 ? void 0 : sourceBlockFormatState.inBlockquote) : currentBlockType === blockType;
39
63
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
40
64
  elemBefore: blockType.icon,
41
65
  elemAfter: shortcut ? /*#__PURE__*/React.createElement(ToolbarKeyboardShortcutHint, {
42
66
  shortcut: shortcut
43
67
  }) : undefined,
44
68
  onClick: onClick,
45
- isSelected: currentBlockType === blockType,
69
+ isSelected: isSelected,
46
70
  isDisabled: isDisabled,
47
71
  ariaKeyshortcuts: shortcut
48
72
  }, formatMessage(blockType.title));
@@ -1,19 +1,58 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useCallback, useMemo } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
4
4
  import { toolbarMessages } from '@atlaskit/editor-common/messages';
5
5
  import { useEditorToolbar } from '@atlaskit/editor-common/toolbar';
6
6
  import { ToolbarDropdownMenu, ToolbarTooltip, TextIcon } from '@atlaskit/editor-toolbar';
7
+ import { fg } from '@atlaskit/platform-feature-flags';
7
8
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
9
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
8
10
  import { toolbarBlockTypesWithRank } from '../../block-types';
9
11
  var usePluginState = function usePluginState(api) {
10
- return useSharedPluginStateWithSelector(api, ['blockType', 'interaction'], function (state) {
11
- var _state$blockTypeState, _state$interactionSta, _state$blockTypeState2;
12
+ // Memoized inside the hook (rather than at module scope) so the gate read
13
+ // inside `toolbarBlockTypesWithRank` doesn't run at module import time —
14
+ // that breaks downstream tests that import preset-default without
15
+ // initializing the FeatureGates client.
16
+ var sourceViewBlockTypeValues = useMemo(function () {
17
+ return Object.values(toolbarBlockTypesWithRank({}));
18
+ }, []);
19
+ var selectPluginState = useCallback(function (state) {
20
+ var _state$interactionSta, _state$blockTypeState, _state$markdownModeSt, _state$markdownModeSt2, _sourceViewBlockTypeV, _sourceViewBlockTypeV2, _sourceViewBlockTypeV3, _state$blockTypeState2;
21
+ var pmCurrentBlockType = ((_state$interactionSta = state.interactionState) === null || _state$interactionSta === void 0 ? void 0 : _state$interactionSta.interactionState) === 'hasNotHadInteraction' && expValEquals('platform_editor_default_toolbar_state', 'isEnabled', true) ? undefined : (_state$blockTypeState = state.blockTypeState) === null || _state$blockTypeState === void 0 ? void 0 : _state$blockTypeState.currentBlockType;
22
+
23
+ // When the markdown bridge is active and the source view is mounted,
24
+ // derive currentBlockType from the CM6 Lezer heading level rather than
25
+ // PM block type state (which reflects the last WYSIWYG cursor position).
26
+ var isMarkdownBridgeEnabled = expValEqualsNoExposure('cc-markdown-mode', 'isEnabled', true) && fg('platform_editor_markdown_compatible_toolbar');
27
+ var sourceFormatState = isMarkdownBridgeEnabled ? (_state$markdownModeSt = state.markdownModeState) === null || _state$markdownModeSt === void 0 ? void 0 : _state$markdownModeSt.sourceBlockFormatState : null;
28
+ // markdownView is authoritative for "in source view" — it flips synchronously
29
+ // on setView. sourceFormatState is null between the view switch and the first
30
+ // CM6 update listener fire, so we can't rely on it as the sentinel.
31
+ var isSourceViewActive = isMarkdownBridgeEnabled && ((_state$markdownModeSt2 = state.markdownModeState) === null || _state$markdownModeSt2 === void 0 ? void 0 : _state$markdownModeSt2.view) === 'syntax';
32
+
33
+ // When in source view, derive currentBlockType from CM6 Lezer state
34
+ // (heading level or blockquote) rather than PM block type state.
35
+ // Guard on sourceFormatState being available — during the race window
36
+ // after switching to source view but before CM6 fires its first update,
37
+ // sourceFormatState is null even though isSourceViewActive is true.
38
+ var currentBlockType = isSourceViewActive && sourceFormatState ? sourceFormatState.headingLevel !== null ? // In a heading, look up the full BlockType by name.
39
+ (_sourceViewBlockTypeV = sourceViewBlockTypeValues.find(function (bt) {
40
+ return bt.name === "heading".concat(sourceFormatState.headingLevel);
41
+ })) !== null && _sourceViewBlockTypeV !== void 0 ? _sourceViewBlockTypeV : null : sourceFormatState.inBlockquote ? // In a blockquote, look up the blockquote BlockType.
42
+ (_sourceViewBlockTypeV2 = sourceViewBlockTypeValues.find(function (bt) {
43
+ return bt.name === 'blockquote';
44
+ })) !== null && _sourceViewBlockTypeV2 !== void 0 ? _sourceViewBlockTypeV2 : null : // Plain paragraph — look up the 'normal' BlockType so the trigger
45
+ // shows "Normal text" (matches WYSIWYG and HeadingButton's
46
+ // Normal-in-source-view selection logic).
47
+ (_sourceViewBlockTypeV3 = sourceViewBlockTypeValues.find(function (bt) {
48
+ return bt.name === 'normal';
49
+ })) !== null && _sourceViewBlockTypeV3 !== void 0 ? _sourceViewBlockTypeV3 : null : isSourceViewActive ? null : pmCurrentBlockType;
12
50
  return {
13
- blockTypesDisabled: (_state$blockTypeState = state.blockTypeState) === null || _state$blockTypeState === void 0 ? void 0 : _state$blockTypeState.blockTypesDisabled,
14
- currentBlockType: ((_state$interactionSta = state.interactionState) === null || _state$interactionSta === void 0 ? void 0 : _state$interactionSta.interactionState) === 'hasNotHadInteraction' && expValEquals('platform_editor_default_toolbar_state', 'isEnabled', true) ? undefined : (_state$blockTypeState2 = state.blockTypeState) === null || _state$blockTypeState2 === void 0 ? void 0 : _state$blockTypeState2.currentBlockType
51
+ blockTypesDisabled: isSourceViewActive && sourceFormatState ? Boolean(sourceFormatState.inCodeBlock) : isSourceViewActive ? false : (_state$blockTypeState2 = state.blockTypeState) === null || _state$blockTypeState2 === void 0 ? void 0 : _state$blockTypeState2.blockTypesDisabled,
52
+ currentBlockType: currentBlockType
15
53
  };
16
- });
54
+ }, [sourceViewBlockTypeValues]);
55
+ return useSharedPluginStateWithSelector(api, ['blockType', 'interaction', 'markdownMode'], selectPluginState);
17
56
  };
18
57
  export var TextStylesMenuButton = function TextStylesMenuButton(_ref) {
19
58
  var _Object$values$find;
@@ -12,6 +12,26 @@ import type { TextBlockTypes } from './pm-plugins/block-types';
12
12
  import type { ClearFormattingInputMethod, InputMethod } from './pm-plugins/commands/block-type';
13
13
  import type { BlockTypeState } from './pm-plugins/main';
14
14
  import type { BlockTypePluginOptions } from './pm-plugins/types';
15
+ /**
16
+ * Minimal duck-typed slice of `@atlassian/editor-plugin-markdown-mode`'s
17
+ * `MarkdownModePlugin` covering only the block-level surface this plugin uses.
18
+ * See `editor-plugin-text-formatting/textFormattingPluginType.ts` for the
19
+ * rationale for not importing the real type.
20
+ */
21
+ type _MarkdownModePluginStub = NextEditorPlugin<'markdownMode', {
22
+ actions: {
23
+ toggleSourceBlockquote: () => boolean;
24
+ toggleSourceHeading: (level: 1 | 2 | 3 | 4 | 5 | 6) => boolean;
25
+ };
26
+ sharedState: {
27
+ sourceBlockFormatState: {
28
+ headingLevel: number | null;
29
+ inBlockquote: boolean;
30
+ inCodeBlock: boolean;
31
+ } | null;
32
+ view: 'syntax' | 'split-view' | 'preview';
33
+ } | undefined;
34
+ }>;
15
35
  export type BlockTypePlugin = NextEditorPlugin<'blockType', {
16
36
  actions: {
17
37
  insertBlockQuote: (inputMethod: InputMethod) => Command;
@@ -30,8 +50,10 @@ export type BlockTypePlugin = NextEditorPlugin<'blockType', {
30
50
  OptionalPlugin<BlockMenuPlugin>,
31
51
  OptionalPlugin<ListPlugin>,
32
52
  OptionalPlugin<SelectionPlugin>,
33
- OptionalPlugin<InteractionPlugin>
53
+ OptionalPlugin<InteractionPlugin>,
54
+ OptionalPlugin<_MarkdownModePluginStub>
34
55
  ];
35
56
  pluginConfiguration: BlockTypePluginOptions | undefined;
36
57
  sharedState: BlockTypeState | undefined;
37
58
  }>;
59
+ export {};
@@ -12,6 +12,26 @@ import type { TextBlockTypes } from './pm-plugins/block-types';
12
12
  import type { ClearFormattingInputMethod, InputMethod } from './pm-plugins/commands/block-type';
13
13
  import type { BlockTypeState } from './pm-plugins/main';
14
14
  import type { BlockTypePluginOptions } from './pm-plugins/types';
15
+ /**
16
+ * Minimal duck-typed slice of `@atlassian/editor-plugin-markdown-mode`'s
17
+ * `MarkdownModePlugin` covering only the block-level surface this plugin uses.
18
+ * See `editor-plugin-text-formatting/textFormattingPluginType.ts` for the
19
+ * rationale for not importing the real type.
20
+ */
21
+ type _MarkdownModePluginStub = NextEditorPlugin<'markdownMode', {
22
+ actions: {
23
+ toggleSourceBlockquote: () => boolean;
24
+ toggleSourceHeading: (level: 1 | 2 | 3 | 4 | 5 | 6) => boolean;
25
+ };
26
+ sharedState: {
27
+ sourceBlockFormatState: {
28
+ headingLevel: number | null;
29
+ inBlockquote: boolean;
30
+ inCodeBlock: boolean;
31
+ } | null;
32
+ view: 'syntax' | 'split-view' | 'preview';
33
+ } | undefined;
34
+ }>;
15
35
  export type BlockTypePlugin = NextEditorPlugin<'blockType', {
16
36
  actions: {
17
37
  insertBlockQuote: (inputMethod: InputMethod) => Command;
@@ -30,8 +50,10 @@ export type BlockTypePlugin = NextEditorPlugin<'blockType', {
30
50
  OptionalPlugin<BlockMenuPlugin>,
31
51
  OptionalPlugin<ListPlugin>,
32
52
  OptionalPlugin<SelectionPlugin>,
33
- OptionalPlugin<InteractionPlugin>
53
+ OptionalPlugin<InteractionPlugin>,
54
+ OptionalPlugin<_MarkdownModePluginStub>
34
55
  ];
35
56
  pluginConfiguration: BlockTypePluginOptions | undefined;
36
57
  sharedState: BlockTypeState | undefined;
37
58
  }>;
59
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-type",
3
- "version": "14.1.7",
3
+ "version": "14.2.0",
4
4
  "description": "BlockType plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -29,7 +29,7 @@
29
29
  ],
30
30
  "atlaskit:src": "src/index.ts",
31
31
  "dependencies": {
32
- "@atlaskit/adf-schema": "^52.14.0",
32
+ "@atlaskit/adf-schema": "^52.15.0",
33
33
  "@atlaskit/css": "^0.19.0",
34
34
  "@atlaskit/editor-plugin-analytics": "^10.1.0",
35
35
  "@atlaskit/editor-plugin-block-menu": "^9.2.0",
@@ -42,7 +42,7 @@
42
42
  "@atlaskit/editor-prosemirror": "^7.3.0",
43
43
  "@atlaskit/editor-shared-styles": "^3.11.0",
44
44
  "@atlaskit/editor-tables": "^2.10.0",
45
- "@atlaskit/editor-toolbar": "^1.7.0",
45
+ "@atlaskit/editor-toolbar": "^1.8.0",
46
46
  "@atlaskit/editor-toolbar-model": "^0.5.0",
47
47
  "@atlaskit/icon": "^35.3.0",
48
48
  "@atlaskit/icon-lab": "^6.12.0",
@@ -50,14 +50,14 @@
50
50
  "@atlaskit/primitives": "^19.0.0",
51
51
  "@atlaskit/prosemirror-history": "^0.2.0",
52
52
  "@atlaskit/prosemirror-input-rules": "^3.7.0",
53
- "@atlaskit/tmp-editor-statsig": "^84.0.0",
53
+ "@atlaskit/tmp-editor-statsig": "^84.3.0",
54
54
  "@atlaskit/tokens": "^13.0.0",
55
55
  "@babel/runtime": "^7.0.0",
56
56
  "@compiled/react": "^0.20.0",
57
57
  "@emotion/react": "^11.7.1"
58
58
  },
59
59
  "peerDependencies": {
60
- "@atlaskit/editor-common": "^114.47.0",
60
+ "@atlaskit/editor-common": "^114.49.0",
61
61
  "react": "^18.2.0",
62
62
  "react-dom": "^18.2.0",
63
63
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
@@ -125,6 +125,9 @@
125
125
  "platform_editor_use_preferences_plugin": {
126
126
  "type": "boolean"
127
127
  },
128
+ "platform_editor_markdown_compatible_toolbar": {
129
+ "type": "boolean"
130
+ },
128
131
  "platform_editor_adf_with_localid": {
129
132
  "type": "boolean"
130
133
  }