@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.
- package/CHANGELOG.md +1 -0
- package/LICENSE.md +13 -0
- package/README.md +30 -0
- package/dist/cjs/actions.js +345 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/plugin.js +91 -0
- package/dist/cjs/pm-plugins/main.js +148 -0
- package/dist/cjs/pm-plugins/plugin-key.js +8 -0
- package/dist/cjs/pm-plugins/types.js +5 -0
- package/dist/cjs/toolbar.js +118 -0
- package/dist/cjs/types.js +5 -0
- package/dist/es2019/actions.js +328 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/plugin.js +75 -0
- package/dist/es2019/pm-plugins/main.js +142 -0
- package/dist/es2019/pm-plugins/plugin-key.js +2 -0
- package/dist/es2019/pm-plugins/types.js +1 -0
- package/dist/es2019/toolbar.js +100 -0
- package/dist/es2019/types.js +1 -0
- package/dist/esm/actions.js +336 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/plugin.js +79 -0
- package/dist/esm/pm-plugins/main.js +141 -0
- package/dist/esm/pm-plugins/plugin-key.js +2 -0
- package/dist/esm/pm-plugins/types.js +1 -0
- package/dist/esm/toolbar.js +108 -0
- package/dist/esm/types.js +1 -0
- package/dist/types/actions.d.ts +22 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/plugin.d.ts +15 -0
- package/dist/types/pm-plugins/main.d.ts +6 -0
- package/dist/types/pm-plugins/plugin-key.d.ts +3 -0
- package/dist/types/pm-plugins/types.d.ts +14 -0
- package/dist/types/toolbar.d.ts +6 -0
- package/dist/types/types.d.ts +13 -0
- package/dist/types-ts4.5/actions.d.ts +22 -0
- package/dist/types-ts4.5/index.d.ts +3 -0
- package/dist/types-ts4.5/plugin.d.ts +18 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +6 -0
- package/dist/types-ts4.5/pm-plugins/plugin-key.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/types.d.ts +14 -0
- package/dist/types-ts4.5/toolbar.d.ts +6 -0
- package/dist/types-ts4.5/types.d.ts +13 -0
- package/package.json +94 -0
- package/report.api.md +73 -0
- 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,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 @@
|
|
|
1
|
+
export {};
|