@atlaskit/editor-plugin-layout 0.1.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/LICENSE.md +13 -0
  3. package/README.md +30 -0
  4. package/dist/cjs/actions.js +345 -0
  5. package/dist/cjs/index.js +12 -0
  6. package/dist/cjs/plugin.js +91 -0
  7. package/dist/cjs/pm-plugins/main.js +148 -0
  8. package/dist/cjs/pm-plugins/plugin-key.js +8 -0
  9. package/dist/cjs/pm-plugins/types.js +5 -0
  10. package/dist/cjs/toolbar.js +118 -0
  11. package/dist/cjs/types.js +5 -0
  12. package/dist/es2019/actions.js +328 -0
  13. package/dist/es2019/index.js +1 -0
  14. package/dist/es2019/plugin.js +75 -0
  15. package/dist/es2019/pm-plugins/main.js +142 -0
  16. package/dist/es2019/pm-plugins/plugin-key.js +2 -0
  17. package/dist/es2019/pm-plugins/types.js +1 -0
  18. package/dist/es2019/toolbar.js +100 -0
  19. package/dist/es2019/types.js +1 -0
  20. package/dist/esm/actions.js +336 -0
  21. package/dist/esm/index.js +1 -0
  22. package/dist/esm/plugin.js +79 -0
  23. package/dist/esm/pm-plugins/main.js +141 -0
  24. package/dist/esm/pm-plugins/plugin-key.js +2 -0
  25. package/dist/esm/pm-plugins/types.js +1 -0
  26. package/dist/esm/toolbar.js +108 -0
  27. package/dist/esm/types.js +1 -0
  28. package/dist/types/actions.d.ts +22 -0
  29. package/dist/types/index.d.ts +3 -0
  30. package/dist/types/plugin.d.ts +15 -0
  31. package/dist/types/pm-plugins/main.d.ts +6 -0
  32. package/dist/types/pm-plugins/plugin-key.d.ts +3 -0
  33. package/dist/types/pm-plugins/types.d.ts +14 -0
  34. package/dist/types/toolbar.d.ts +6 -0
  35. package/dist/types/types.d.ts +13 -0
  36. package/dist/types-ts4.5/actions.d.ts +22 -0
  37. package/dist/types-ts4.5/index.d.ts +3 -0
  38. package/dist/types-ts4.5/plugin.d.ts +18 -0
  39. package/dist/types-ts4.5/pm-plugins/main.d.ts +6 -0
  40. package/dist/types-ts4.5/pm-plugins/plugin-key.d.ts +3 -0
  41. package/dist/types-ts4.5/pm-plugins/types.d.ts +14 -0
  42. package/dist/types-ts4.5/toolbar.d.ts +6 -0
  43. package/dist/types-ts4.5/types.d.ts +13 -0
  44. package/package.json +94 -0
  45. package/report.api.md +73 -0
  46. package/tmp/api-report-tmp.d.ts +43 -0
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.layoutToolbarTitle = exports.buildToolbar = void 0;
9
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
10
+ var _messages = _interopRequireWildcard(require("@atlaskit/editor-common/messages"));
11
+ var _utils = require("@atlaskit/editor-prosemirror/utils");
12
+ var _layoutSingle = _interopRequireDefault(require("@atlaskit/icon/glyph/editor/layout-single"));
13
+ var _layoutThreeEqual = _interopRequireDefault(require("@atlaskit/icon/glyph/editor/layout-three-equal"));
14
+ var _layoutThreeWithSidebars = _interopRequireDefault(require("@atlaskit/icon/glyph/editor/layout-three-with-sidebars"));
15
+ var _layoutTwoEqual = _interopRequireDefault(require("@atlaskit/icon/glyph/editor/layout-two-equal"));
16
+ var _layoutTwoLeftSidebar = _interopRequireDefault(require("@atlaskit/icon/glyph/editor/layout-two-left-sidebar"));
17
+ var _layoutTwoRightSidebar = _interopRequireDefault(require("@atlaskit/icon/glyph/editor/layout-two-right-sidebar"));
18
+ var _remove = _interopRequireDefault(require("@atlaskit/icon/glyph/editor/remove"));
19
+ var _actions = require("./actions");
20
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
21
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
22
+ var LAYOUT_TYPES = [{
23
+ id: 'editor.layout.twoEquals',
24
+ type: 'two_equal',
25
+ title: _messages.layoutMessages.twoColumns,
26
+ icon: _layoutTwoEqual.default
27
+ }, {
28
+ id: 'editor.layout.threeEquals',
29
+ type: 'three_equal',
30
+ title: _messages.layoutMessages.threeColumns,
31
+ icon: _layoutThreeEqual.default
32
+ }];
33
+ var LAYOUT_TYPES_WITH_SINGLE_COL = [{
34
+ id: 'editor.layout.singeLayout',
35
+ type: 'single',
36
+ title: _messages.layoutMessages.singleColumn,
37
+ icon: _layoutSingle.default
38
+ }].concat(LAYOUT_TYPES);
39
+ var SIDEBAR_LAYOUT_TYPES = [{
40
+ id: 'editor.layout.twoRightSidebar',
41
+ type: 'two_right_sidebar',
42
+ title: _messages.layoutMessages.rightSidebar,
43
+ icon: _layoutTwoRightSidebar.default
44
+ }, {
45
+ id: 'editor.layout.twoLeftSidebar',
46
+ type: 'two_left_sidebar',
47
+ title: _messages.layoutMessages.leftSidebar,
48
+ icon: _layoutTwoLeftSidebar.default
49
+ }, {
50
+ id: 'editor.layout.threeWithSidebars',
51
+ type: 'three_with_sidebars',
52
+ title: _messages.layoutMessages.threeColumnsWithSidebars,
53
+ icon: _layoutThreeWithSidebars.default
54
+ }];
55
+ var buildLayoutButton = function buildLayoutButton(intl, item, currentLayout, editorAnalyticsAPI) {
56
+ return {
57
+ id: item.id,
58
+ type: 'button',
59
+ icon: item.icon,
60
+ testId: item.title.id ? "".concat(item.title.id) : undefined,
61
+ title: intl.formatMessage(item.title),
62
+ onClick: (0, _actions.setPresetLayout)(editorAnalyticsAPI)(item.type),
63
+ selected: !!currentLayout && currentLayout === item.type,
64
+ tabIndex: null
65
+ };
66
+ };
67
+ var layoutToolbarTitle = exports.layoutToolbarTitle = 'Layout floating controls';
68
+ var buildToolbar = exports.buildToolbar = function buildToolbar(state, intl, pos, _allowBreakout, addSidebarLayouts, allowSingleColumnLayout, api) {
69
+ var _api$decorations$acti, _api$decorations, _api$analytics;
70
+ var _ref = (_api$decorations$acti = api === null || api === void 0 || (_api$decorations = api.decorations) === null || _api$decorations === void 0 ? void 0 : _api$decorations.actions) !== null && _api$decorations$acti !== void 0 ? _api$decorations$acti : {},
71
+ hoverDecoration = _ref.hoverDecoration;
72
+ var editorAnalyticsAPI = api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
73
+ var node = state.doc.nodeAt(pos);
74
+ if (node) {
75
+ var currentLayout = (0, _actions.getPresetLayout)(node);
76
+ var separator = {
77
+ type: 'separator'
78
+ };
79
+ var nodeType = state.schema.nodes.layoutSection;
80
+ var deleteButton = {
81
+ id: 'editor.layout.delete',
82
+ type: 'button',
83
+ appearance: 'danger',
84
+ focusEditoronEnter: true,
85
+ icon: _remove.default,
86
+ testId: _messages.default.remove.id,
87
+ title: intl.formatMessage(_messages.default.remove),
88
+ onClick: (0, _actions.deleteActiveLayoutNode)(editorAnalyticsAPI),
89
+ onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true),
90
+ onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false),
91
+ onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true),
92
+ onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false),
93
+ tabIndex: null
94
+ };
95
+ var layoutTypes = allowSingleColumnLayout ? LAYOUT_TYPES_WITH_SINGLE_COL : LAYOUT_TYPES;
96
+ return {
97
+ title: layoutToolbarTitle,
98
+ getDomRef: function getDomRef(view) {
99
+ return (0, _utils.findDomRefAtPos)(pos, view.domAtPos.bind(view));
100
+ },
101
+ nodeType: nodeType,
102
+ items: [].concat((0, _toConsumableArray2.default)(layoutTypes.map(function (i) {
103
+ return buildLayoutButton(intl, i, currentLayout, editorAnalyticsAPI);
104
+ })), (0, _toConsumableArray2.default)(addSidebarLayouts ? SIDEBAR_LAYOUT_TYPES.map(function (i) {
105
+ return buildLayoutButton(intl, i, currentLayout, editorAnalyticsAPI);
106
+ }) : []), [{
107
+ type: 'copy-button',
108
+ items: [separator, {
109
+ state: state,
110
+ formatMessage: intl.formatMessage,
111
+ nodeType: nodeType
112
+ }]
113
+ }, separator, deleteButton]),
114
+ scrollable: true
115
+ };
116
+ }
117
+ return;
118
+ };
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
@@ -0,0 +1,328 @@
1
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, LAYOUT_TYPE } from '@atlaskit/editor-common/analytics';
2
+ import { withAnalytics } from '@atlaskit/editor-common/editor-analytics';
3
+ import { flatmap, getStepRange, isEmptyDocument, mapChildren } from '@atlaskit/editor-common/utils';
4
+ import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
5
+ import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
6
+ import { safeInsert } from '@atlaskit/editor-prosemirror/utils';
7
+ import { pluginKey } from './pm-plugins/plugin-key';
8
+ export const ONE_COL_LAYOUTS = ['single'];
9
+ export const TWO_COL_LAYOUTS = ['two_equal', 'two_left_sidebar', 'two_right_sidebar'];
10
+ export const THREE_COL_LAYOUTS = ['three_equal', 'three_with_sidebars'];
11
+ const getWidthsForPreset = presetLayout => {
12
+ switch (presetLayout) {
13
+ case 'single':
14
+ return [100];
15
+ case 'two_equal':
16
+ return [50, 50];
17
+ case 'three_equal':
18
+ return [33.33, 33.33, 33.33];
19
+ case 'two_left_sidebar':
20
+ return [33.33, 66.66];
21
+ case 'two_right_sidebar':
22
+ return [66.66, 33.33];
23
+ case 'three_with_sidebars':
24
+ return [25, 50, 25];
25
+ }
26
+ };
27
+
28
+ /**
29
+ * Finds layout preset based on the width attrs of all the layoutColumn nodes
30
+ * inside the layoutSection node
31
+ */
32
+ export const getPresetLayout = section => {
33
+ const widths = mapChildren(section, column => column.attrs.width).join(',');
34
+ switch (widths) {
35
+ case '100':
36
+ return 'single';
37
+ case '33.33,33.33,33.33':
38
+ return 'three_equal';
39
+ case '25,50,25':
40
+ return 'three_with_sidebars';
41
+ case '50,50':
42
+ return 'two_equal';
43
+ case '33.33,66.66':
44
+ return 'two_left_sidebar';
45
+ case '66.66,33.33':
46
+ return 'two_right_sidebar';
47
+ }
48
+ return;
49
+ };
50
+ export const getSelectedLayout = (maybeLayoutSection, current) => {
51
+ if (maybeLayoutSection && getPresetLayout(maybeLayoutSection)) {
52
+ return getPresetLayout(maybeLayoutSection) || current;
53
+ }
54
+ return current;
55
+ };
56
+ export const createDefaultLayoutSection = state => {
57
+ const {
58
+ layoutSection,
59
+ layoutColumn
60
+ } = state.schema.nodes;
61
+
62
+ // create a 50-50 layout by default
63
+ const columns = Fragment.fromArray([layoutColumn.createAndFill({
64
+ width: 50
65
+ }), layoutColumn.createAndFill({
66
+ width: 50
67
+ })]);
68
+ return layoutSection.createAndFill(undefined, columns);
69
+ };
70
+ export const insertLayoutColumns = (state, dispatch) => {
71
+ if (dispatch) {
72
+ dispatch(safeInsert(createDefaultLayoutSection(state))(state.tr));
73
+ }
74
+ return true;
75
+ };
76
+ export const insertLayoutColumnsWithAnalytics = editorAnalyticsAPI => inputMethod => withAnalytics(editorAnalyticsAPI, {
77
+ action: ACTION.INSERTED,
78
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
79
+ actionSubjectId: ACTION_SUBJECT_ID.LAYOUT,
80
+ attributes: {
81
+ inputMethod
82
+ },
83
+ eventType: EVENT_TYPE.TRACK
84
+ })(insertLayoutColumns);
85
+
86
+ /**
87
+ * Add a column to the right of existing layout
88
+ */
89
+ function addColumn(schema, pos) {
90
+ return tr => {
91
+ tr.replaceWith(tr.mapping.map(pos), tr.mapping.map(pos), schema.nodes.layoutColumn.createAndFill());
92
+ };
93
+ }
94
+ function removeLastColumnInLayout(column, columnPos, insideRightEdgePos) {
95
+ return tr => {
96
+ if (isEmptyDocument(column)) {
97
+ tr.replaceRange(tr.mapping.map(columnPos - 1), tr.mapping.map(insideRightEdgePos), Slice.empty);
98
+ } else {
99
+ tr.replaceRange(tr.mapping.map(columnPos - 1), tr.mapping.map(columnPos + 1), Slice.empty);
100
+ }
101
+ };
102
+ }
103
+ const fromTwoColsToThree = addColumn;
104
+ const fromOneColToTwo = addColumn;
105
+ const fromTwoColsToOne = removeLastColumnInLayout;
106
+ const fromThreeColsToTwo = removeLastColumnInLayout;
107
+ const fromOneColToThree = (schema, pos) => {
108
+ return tr => {
109
+ addColumn(schema, pos)(tr);
110
+ addColumn(schema, pos)(tr);
111
+ };
112
+ };
113
+ const fromThreeColstoOne = (node, tr, insideRightEdgePos) => {
114
+ const thirdColumn = node.content.child(2);
115
+ fromThreeColsToTwo(thirdColumn, insideRightEdgePos - thirdColumn.nodeSize, insideRightEdgePos)(tr);
116
+ const secondColumn = node.content.child(1);
117
+ fromTwoColsToOne(secondColumn, insideRightEdgePos - thirdColumn.nodeSize - secondColumn.nodeSize, insideRightEdgePos)(tr);
118
+ };
119
+
120
+ /**
121
+ * Handles switching from 2 -> 3 cols, or 3 -> 2 cols
122
+ * Switching from 2 -> 3 just adds a new one at the end
123
+ * Switching from 3 -> 2 moves all the content of the third col inside the second before
124
+ * removing it
125
+ */
126
+ function forceColumnStructure(state, node, pos, presetLayout) {
127
+ const tr = state.tr;
128
+ const insideRightEdgeOfLayoutSection = pos + node.nodeSize - 1;
129
+ const numCols = node.childCount;
130
+
131
+ // 3 columns -> 2 columns
132
+ if (TWO_COL_LAYOUTS.indexOf(presetLayout) >= 0 && numCols === 3) {
133
+ const thirdColumn = node.content.child(2);
134
+ const columnPos = insideRightEdgeOfLayoutSection - thirdColumn.nodeSize;
135
+ fromThreeColsToTwo(thirdColumn, columnPos, insideRightEdgeOfLayoutSection)(tr);
136
+
137
+ // 2 columns -> 3 columns
138
+ } else if (THREE_COL_LAYOUTS.indexOf(presetLayout) >= 0 && numCols === 2) {
139
+ fromTwoColsToThree(state.schema, insideRightEdgeOfLayoutSection)(tr);
140
+
141
+ // 2 columns -> 1 column
142
+ } else if (ONE_COL_LAYOUTS.indexOf(presetLayout) >= 0 && numCols === 2) {
143
+ const secondColumn = node.content.child(1);
144
+ const columnPos = insideRightEdgeOfLayoutSection - secondColumn.nodeSize;
145
+ fromTwoColsToOne(secondColumn, columnPos, insideRightEdgeOfLayoutSection)(tr);
146
+
147
+ // 3 columns -> 1 column
148
+ } else if (ONE_COL_LAYOUTS.indexOf(presetLayout) >= 0 && numCols === 3) {
149
+ fromThreeColstoOne(node, tr, insideRightEdgeOfLayoutSection);
150
+
151
+ // 1 column -> 2 columns
152
+ } else if (TWO_COL_LAYOUTS.indexOf(presetLayout) >= 0 && numCols === 1) {
153
+ fromOneColToTwo(state.schema, insideRightEdgeOfLayoutSection)(tr);
154
+ // 1 column -> 3 columns
155
+ } else if (THREE_COL_LAYOUTS.indexOf(presetLayout) >= 0 && numCols === 1) {
156
+ fromOneColToThree(state.schema, insideRightEdgeOfLayoutSection)(tr);
157
+ }
158
+ return tr;
159
+ }
160
+ function columnWidth(node, schema, widths) {
161
+ const {
162
+ layoutColumn
163
+ } = schema.nodes;
164
+ const truncatedWidths = widths.map(w => Number(w.toFixed(2)));
165
+ return flatmap(node.content, (column, idx) => {
166
+ if (column.type === layoutColumn) {
167
+ return layoutColumn.create({
168
+ ...column.attrs,
169
+ width: truncatedWidths[idx]
170
+ }, column.content, column.marks);
171
+ } else {
172
+ return column;
173
+ }
174
+ });
175
+ }
176
+ function forceColumnWidths(state, tr, pos, presetLayout) {
177
+ const node = tr.doc.nodeAt(pos);
178
+ if (!node) {
179
+ return tr;
180
+ }
181
+ return tr.replaceWith(pos + 1, pos + node.nodeSize - 1, columnWidth(node, state.schema, getWidthsForPreset(presetLayout)));
182
+ }
183
+ export function forceSectionToPresetLayout(state, node, pos, presetLayout) {
184
+ let tr = forceColumnStructure(state, node, pos, presetLayout);
185
+
186
+ // save the selection here, since forcing column widths causes a change over the
187
+ // entire layoutSection, which remaps selection to the end. not remapping here
188
+ // is safe because the structure is no longer changing.
189
+ const selection = tr.selection;
190
+ tr = forceColumnWidths(state, tr, pos, presetLayout);
191
+ const selectionPos$ = tr.doc.resolve(selection.$from.pos);
192
+ return tr.setSelection(state.selection instanceof NodeSelection ? new NodeSelection(selectionPos$) : new TextSelection(selectionPos$));
193
+ }
194
+ export const setPresetLayout = editorAnalyticsAPI => layout => (state, dispatch) => {
195
+ const {
196
+ pos,
197
+ selectedLayout
198
+ } = pluginKey.getState(state);
199
+ if (selectedLayout === layout || pos === null) {
200
+ return false;
201
+ }
202
+ const node = state.doc.nodeAt(pos);
203
+ if (!node) {
204
+ return false;
205
+ }
206
+ let tr = forceSectionToPresetLayout(state, node, pos, layout);
207
+ if (tr) {
208
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
209
+ action: ACTION.CHANGED_LAYOUT,
210
+ actionSubject: ACTION_SUBJECT.LAYOUT,
211
+ attributes: {
212
+ previousLayout: formatLayoutName(selectedLayout),
213
+ newLayout: formatLayoutName(layout)
214
+ },
215
+ eventType: EVENT_TYPE.TRACK
216
+ })(tr);
217
+ tr.setMeta('scrollIntoView', false);
218
+ if (dispatch) {
219
+ dispatch(tr);
220
+ }
221
+ return true;
222
+ }
223
+ return false;
224
+ };
225
+ function layoutNeedChanges(node) {
226
+ return !getPresetLayout(node);
227
+ }
228
+ function getLayoutChange(node, pos, schema) {
229
+ if (node.type === schema.nodes.layoutSection) {
230
+ if (!layoutNeedChanges(node)) {
231
+ return;
232
+ }
233
+ const presetLayout = node.childCount === 2 ? 'two_equal' : node.childCount === 3 ? 'three_equal' : 'single';
234
+ const fixedColumns = columnWidth(node, schema, getWidthsForPreset(presetLayout));
235
+ return {
236
+ from: pos + 1,
237
+ to: pos + node.nodeSize - 1,
238
+ slice: new Slice(fixedColumns, 0, 0)
239
+ };
240
+ }
241
+ }
242
+ export const fixColumnSizes = (changedTr, state) => {
243
+ const {
244
+ layoutSection
245
+ } = state.schema.nodes;
246
+ let change;
247
+ const range = getStepRange(changedTr);
248
+ if (!range) {
249
+ return undefined;
250
+ }
251
+ changedTr.doc.nodesBetween(range.from, range.to, (node, pos) => {
252
+ if (node.type !== layoutSection) {
253
+ return true; // Check all internal nodes expect for layout section
254
+ }
255
+ // Node is a section
256
+ if (layoutNeedChanges(node)) {
257
+ change = getLayoutChange(node, pos, state.schema);
258
+ }
259
+ return false; // We dont go deep, We dont accept nested layouts
260
+ });
261
+
262
+ // Hack to prevent: https://product-fabric.atlassian.net/browse/ED-7523
263
+ // By default prosemirror try to recreate the node with the default attributes
264
+ // The default attribute is invalid adf though. when this happen the node after
265
+ // current position is a layout section
266
+ const $pos = changedTr.doc.resolve(range.to);
267
+ if ($pos.depth > 0) {
268
+ // 'range.to' position could resolve to doc, in this ResolvedPos.after will throws
269
+ const pos = $pos.after();
270
+ const node = changedTr.doc.nodeAt(pos);
271
+ if (node && node.type === layoutSection && layoutNeedChanges(node)) {
272
+ change = getLayoutChange(node, pos, state.schema);
273
+ }
274
+ }
275
+ return change;
276
+ };
277
+ export const fixColumnStructure = state => {
278
+ const {
279
+ pos,
280
+ selectedLayout
281
+ } = pluginKey.getState(state);
282
+ if (pos !== null && selectedLayout) {
283
+ const node = state.doc.nodeAt(pos);
284
+ if (node && node.childCount !== getWidthsForPreset(selectedLayout).length) {
285
+ return forceSectionToPresetLayout(state, node, pos, selectedLayout);
286
+ }
287
+ }
288
+ return;
289
+ };
290
+ export const deleteActiveLayoutNode = editorAnalyticsAPI => (state, dispatch) => {
291
+ const {
292
+ pos,
293
+ selectedLayout
294
+ } = pluginKey.getState(state);
295
+ if (pos !== null) {
296
+ const node = state.doc.nodeAt(pos);
297
+ if (dispatch) {
298
+ let tr = state.tr.delete(pos, pos + node.nodeSize);
299
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
300
+ action: ACTION.DELETED,
301
+ actionSubject: ACTION_SUBJECT.LAYOUT,
302
+ attributes: {
303
+ layout: formatLayoutName(selectedLayout)
304
+ },
305
+ eventType: EVENT_TYPE.TRACK
306
+ })(tr);
307
+ dispatch(tr);
308
+ }
309
+ return true;
310
+ }
311
+ return false;
312
+ };
313
+ const formatLayoutName = layout => {
314
+ switch (layout) {
315
+ case 'single':
316
+ return LAYOUT_TYPE.SINGLE_COL;
317
+ case 'two_equal':
318
+ return LAYOUT_TYPE.TWO_COLS_EQUAL;
319
+ case 'three_equal':
320
+ return LAYOUT_TYPE.THREE_COLS_EQUAL;
321
+ case 'two_left_sidebar':
322
+ return LAYOUT_TYPE.LEFT_SIDEBAR;
323
+ case 'two_right_sidebar':
324
+ return LAYOUT_TYPE.RIGHT_SIDEBAR;
325
+ case 'three_with_sidebars':
326
+ return LAYOUT_TYPE.THREE_WITH_SIDEBARS;
327
+ }
328
+ };
@@ -0,0 +1 @@
1
+ export { layoutPlugin } from './plugin';
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import { layoutColumn, layoutSection } from '@atlaskit/adf-schema';
3
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
+ import { toolbarInsertBlockMessages as messages } from '@atlaskit/editor-common/messages';
5
+ import { IconLayout } from '@atlaskit/editor-common/quick-insert';
6
+ import { createDefaultLayoutSection, insertLayoutColumnsWithAnalytics } from './actions';
7
+ import { default as createLayoutPlugin } from './pm-plugins/main';
8
+ import { pluginKey } from './pm-plugins/plugin-key';
9
+ import { buildToolbar } from './toolbar';
10
+ export { pluginKey };
11
+ export const layoutPlugin = ({
12
+ config: options = {},
13
+ api
14
+ }) => {
15
+ var _api$analytics;
16
+ return {
17
+ name: 'layout',
18
+ nodes() {
19
+ return [{
20
+ name: 'layoutSection',
21
+ node: layoutSection
22
+ }, {
23
+ name: 'layoutColumn',
24
+ node: layoutColumn
25
+ }];
26
+ },
27
+ pmPlugins() {
28
+ return [{
29
+ name: 'layout',
30
+ plugin: () => createLayoutPlugin(options)
31
+ }];
32
+ },
33
+ actions: {
34
+ insertLayoutColumns: insertLayoutColumnsWithAnalytics(api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions)
35
+ },
36
+ pluginsOptions: {
37
+ floatingToolbar(state, intl) {
38
+ const {
39
+ pos,
40
+ allowBreakout,
41
+ addSidebarLayouts,
42
+ allowSingleColumnLayout
43
+ } = pluginKey.getState(state);
44
+ if (pos !== null) {
45
+ return buildToolbar(state, intl, pos, allowBreakout, addSidebarLayouts, allowSingleColumnLayout, api);
46
+ }
47
+ return undefined;
48
+ },
49
+ quickInsert: ({
50
+ formatMessage
51
+ }) => [{
52
+ id: 'layout',
53
+ title: formatMessage(messages.columns),
54
+ description: formatMessage(messages.columnsDescription),
55
+ keywords: ['column', 'section'],
56
+ priority: 1100,
57
+ icon: () => /*#__PURE__*/React.createElement(IconLayout, null),
58
+ action(insert, state) {
59
+ var _api$analytics2, _api$analytics2$actio;
60
+ const tr = insert(createDefaultLayoutSection(state));
61
+ api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : (_api$analytics2$actio = _api$analytics2.actions) === null || _api$analytics2$actio === void 0 ? void 0 : _api$analytics2$actio.attachAnalyticsEvent({
62
+ action: ACTION.INSERTED,
63
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
64
+ actionSubjectId: ACTION_SUBJECT_ID.LAYOUT,
65
+ attributes: {
66
+ inputMethod: INPUT_METHOD.QUICK_INSERT
67
+ },
68
+ eventType: EVENT_TYPE.TRACK
69
+ })(tr);
70
+ return tr;
71
+ }
72
+ }]
73
+ }
74
+ };
75
+ };
@@ -0,0 +1,142 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { createSelectionClickHandler } from '@atlaskit/editor-common/selection';
3
+ import { filterCommand as filter } from '@atlaskit/editor-common/utils';
4
+ import { keydownHandler } from '@atlaskit/editor-prosemirror/keymap';
5
+ import { Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
6
+ import { findParentNodeOfType, findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
7
+ import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
8
+ import { fixColumnSizes, fixColumnStructure, getSelectedLayout } from '../actions';
9
+ import { pluginKey } from './plugin-key';
10
+ export const DEFAULT_LAYOUT = 'two_equal';
11
+ const isWholeSelectionInsideLayoutColumn = state => {
12
+ // Since findParentNodeOfType doesn't check if selection.to shares the parent, we do this check ourselves
13
+ const fromParent = findParentNodeOfType(state.schema.nodes.layoutColumn)(state.selection);
14
+ if (fromParent) {
15
+ const isToPosInsideSameLayoutColumn = state.selection.from < fromParent.pos + fromParent.node.nodeSize;
16
+ return isToPosInsideSameLayoutColumn;
17
+ }
18
+ return false;
19
+ };
20
+ const moveCursorToNextColumn = (state, dispatch) => {
21
+ const {
22
+ selection
23
+ } = state;
24
+ const {
25
+ schema: {
26
+ nodes: {
27
+ layoutColumn,
28
+ layoutSection
29
+ }
30
+ }
31
+ } = state;
32
+ const section = findParentNodeOfType(layoutSection)(selection);
33
+ const column = findParentNodeOfType(layoutColumn)(selection);
34
+ if (column.node !== section.node.lastChild) {
35
+ const $nextColumn = state.doc.resolve(column.pos + column.node.nodeSize);
36
+ const shiftedSelection = TextSelection.findFrom($nextColumn, 1);
37
+ if (dispatch) {
38
+ dispatch(state.tr.setSelection(shiftedSelection));
39
+ }
40
+ }
41
+ return true;
42
+ };
43
+
44
+ // TODO: Look at memoize-one-ing this fn
45
+ const getNodeDecoration = (pos, node) => [Decoration.node(pos, pos + node.nodeSize, {
46
+ class: 'selected'
47
+ })];
48
+ const getInitialPluginState = (options, state) => {
49
+ const maybeLayoutSection = findParentNodeOfType(state.schema.nodes.layoutSection)(state.selection);
50
+ const allowBreakout = options.allowBreakout || false;
51
+ const addSidebarLayouts = options.UNSAFE_addSidebarLayouts || false;
52
+ const allowSingleColumnLayout = options.UNSAFE_allowSingleColumnLayout || false;
53
+ const pos = maybeLayoutSection ? maybeLayoutSection.pos : null;
54
+ const selectedLayout = getSelectedLayout(maybeLayoutSection && maybeLayoutSection.node, DEFAULT_LAYOUT);
55
+ return {
56
+ pos,
57
+ allowBreakout,
58
+ addSidebarLayouts,
59
+ selectedLayout,
60
+ allowSingleColumnLayout
61
+ };
62
+ };
63
+ export default (options => new SafePlugin({
64
+ key: pluginKey,
65
+ state: {
66
+ init: (_, state) => getInitialPluginState(options, state),
67
+ apply: (tr, pluginState, _oldState, newState) => {
68
+ if (tr.docChanged || tr.selectionSet) {
69
+ const {
70
+ schema: {
71
+ nodes: {
72
+ layoutSection
73
+ }
74
+ },
75
+ selection
76
+ } = newState;
77
+ const maybeLayoutSection = findParentNodeOfType(layoutSection)(selection) || findSelectedNodeOfType([layoutSection])(selection);
78
+ const newPluginState = {
79
+ ...pluginState,
80
+ pos: maybeLayoutSection ? maybeLayoutSection.pos : null,
81
+ selectedLayout: getSelectedLayout(maybeLayoutSection && maybeLayoutSection.node, pluginState.selectedLayout)
82
+ };
83
+ return newPluginState;
84
+ }
85
+ return pluginState;
86
+ }
87
+ },
88
+ props: {
89
+ decorations(state) {
90
+ const layoutState = pluginKey.getState(state);
91
+ if (layoutState.pos !== null) {
92
+ return DecorationSet.create(state.doc, getNodeDecoration(layoutState.pos, state.doc.nodeAt(layoutState.pos)));
93
+ }
94
+ return undefined;
95
+ },
96
+ handleKeyDown: keydownHandler({
97
+ Tab: filter(isWholeSelectionInsideLayoutColumn, moveCursorToNextColumn)
98
+ }),
99
+ handleClickOn: createSelectionClickHandler(['layoutColumn'], target => target.hasAttribute('data-layout-section') || target.hasAttribute('data-layout-column'), {
100
+ useLongPressSelection: options.useLongPressSelection || false,
101
+ getNodeSelectionPos: (state, nodePos) => state.doc.resolve(nodePos).before()
102
+ })
103
+ },
104
+ appendTransaction: (transactions, _oldState, newState) => {
105
+ let changes = [];
106
+ transactions.forEach(prevTr => {
107
+ // remap change segments across the transaction set
108
+ changes.forEach(change => {
109
+ return {
110
+ from: prevTr.mapping.map(change.from),
111
+ to: prevTr.mapping.map(change.to),
112
+ slice: change.slice
113
+ };
114
+ });
115
+
116
+ // don't consider transactions that don't mutate
117
+ if (!prevTr.docChanged) {
118
+ return;
119
+ }
120
+ const change = fixColumnSizes(prevTr, newState);
121
+ if (change) {
122
+ changes.push(change);
123
+ }
124
+ });
125
+ if (changes.length) {
126
+ let tr = newState.tr;
127
+ const selection = newState.selection.toJSON();
128
+ changes.forEach(change => {
129
+ tr.replaceRange(change.from, change.to, change.slice);
130
+ });
131
+
132
+ // selecting and deleting across columns in 3 col layouts can remove
133
+ // a layoutColumn so we fix the structure here
134
+ tr = fixColumnStructure(newState) || tr;
135
+ if (tr.docChanged) {
136
+ tr.setSelection(Selection.fromJSON(tr.doc, selection));
137
+ return tr;
138
+ }
139
+ }
140
+ return;
141
+ }
142
+ }));
@@ -0,0 +1,2 @@
1
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
2
+ export const pluginKey = new PluginKey('layout');
@@ -0,0 +1 @@
1
+ export {};