@atlaskit/editor-plugin-table 7.10.1 → 7.11.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 (62) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/commands/insert.js +4 -7
  3. package/dist/cjs/commands/misc.js +10 -3
  4. package/dist/cjs/nodeviews/TableComponent.js +23 -2
  5. package/dist/cjs/plugin.js +7 -1
  6. package/dist/cjs/pm-plugins/analytics/plugin.js +17 -22
  7. package/dist/cjs/pm-plugins/view-mode-sort/consts.js +9 -0
  8. package/dist/cjs/pm-plugins/view-mode-sort/index.js +304 -0
  9. package/dist/cjs/pm-plugins/view-mode-sort/plugin-key.js +8 -0
  10. package/dist/cjs/pm-plugins/view-mode-sort/types.js +5 -0
  11. package/dist/cjs/pm-plugins/view-mode-sort/utils.js +106 -0
  12. package/dist/cjs/ui/common-styles.js +22 -15
  13. package/dist/es2019/commands/insert.js +4 -7
  14. package/dist/es2019/commands/misc.js +10 -3
  15. package/dist/es2019/nodeviews/TableComponent.js +24 -2
  16. package/dist/es2019/plugin.js +7 -1
  17. package/dist/es2019/pm-plugins/analytics/plugin.js +21 -26
  18. package/dist/es2019/pm-plugins/view-mode-sort/consts.js +3 -0
  19. package/dist/es2019/pm-plugins/view-mode-sort/index.js +243 -0
  20. package/dist/es2019/pm-plugins/view-mode-sort/plugin-key.js +2 -0
  21. package/dist/es2019/pm-plugins/view-mode-sort/types.js +1 -0
  22. package/dist/es2019/pm-plugins/view-mode-sort/utils.js +98 -0
  23. package/dist/es2019/ui/common-styles.js +35 -0
  24. package/dist/esm/commands/insert.js +4 -7
  25. package/dist/esm/commands/misc.js +10 -3
  26. package/dist/esm/nodeviews/TableComponent.js +23 -2
  27. package/dist/esm/plugin.js +7 -1
  28. package/dist/esm/pm-plugins/analytics/plugin.js +17 -22
  29. package/dist/esm/pm-plugins/view-mode-sort/consts.js +3 -0
  30. package/dist/esm/pm-plugins/view-mode-sort/index.js +299 -0
  31. package/dist/esm/pm-plugins/view-mode-sort/plugin-key.js +2 -0
  32. package/dist/esm/pm-plugins/view-mode-sort/types.js +1 -0
  33. package/dist/esm/pm-plugins/view-mode-sort/utils.js +99 -0
  34. package/dist/esm/ui/common-styles.js +15 -8
  35. package/dist/types/pm-plugins/view-mode-sort/consts.d.ts +3 -0
  36. package/dist/types/pm-plugins/view-mode-sort/index.d.ts +10 -0
  37. package/dist/types/pm-plugins/view-mode-sort/plugin-key.d.ts +3 -0
  38. package/dist/types/pm-plugins/view-mode-sort/types.d.ts +17 -0
  39. package/dist/types/pm-plugins/view-mode-sort/utils.d.ts +15 -0
  40. package/dist/types/ui/TableFloatingColumnControls/ColumnControls/index.d.ts +2 -2
  41. package/dist/types/ui/TableFloatingControls/CornerControls/DragCornerControls.d.ts +4 -4
  42. package/dist/types/ui/TableFloatingControls/index.d.ts +2 -2
  43. package/dist/types-ts4.5/pm-plugins/view-mode-sort/consts.d.ts +3 -0
  44. package/dist/types-ts4.5/pm-plugins/view-mode-sort/index.d.ts +10 -0
  45. package/dist/types-ts4.5/pm-plugins/view-mode-sort/plugin-key.d.ts +3 -0
  46. package/dist/types-ts4.5/pm-plugins/view-mode-sort/types.d.ts +21 -0
  47. package/dist/types-ts4.5/pm-plugins/view-mode-sort/utils.d.ts +15 -0
  48. package/dist/types-ts4.5/ui/TableFloatingColumnControls/ColumnControls/index.d.ts +2 -2
  49. package/dist/types-ts4.5/ui/TableFloatingControls/CornerControls/DragCornerControls.d.ts +4 -4
  50. package/dist/types-ts4.5/ui/TableFloatingControls/index.d.ts +2 -2
  51. package/package.json +8 -8
  52. package/src/commands/insert.ts +7 -13
  53. package/src/commands/misc.ts +14 -8
  54. package/src/nodeviews/TableComponent.tsx +22 -0
  55. package/src/plugin.tsx +12 -3
  56. package/src/pm-plugins/analytics/plugin.ts +24 -33
  57. package/src/pm-plugins/view-mode-sort/consts.ts +3 -0
  58. package/src/pm-plugins/view-mode-sort/index.ts +257 -0
  59. package/src/pm-plugins/view-mode-sort/plugin-key.ts +6 -0
  60. package/src/pm-plugins/view-mode-sort/types.ts +23 -0
  61. package/src/pm-plugins/view-mode-sort/utils.ts +120 -0
  62. package/src/ui/common-styles.ts +36 -0
@@ -0,0 +1,257 @@
1
+ /**
2
+ * This plugin allows sorting of table nodes in the Editor without modifying the underlying ProseMirror document.
3
+ * Instead of making changes to the ProseMirror document, the plugin sorts the table rows in the DOM. This allows the sorting to be
4
+ * visible to the user without affecting the document's content.
5
+ */
6
+
7
+ import { createElement } from 'react';
8
+
9
+ import ReactDOM from 'react-dom';
10
+ import { RawIntlProvider } from 'react-intl-next';
11
+
12
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
13
+ import { SortingIcon } from '@atlaskit/editor-common/table';
14
+ import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
15
+ import { SortOrder } from '@atlaskit/editor-common/types';
16
+ import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
17
+ import { TableMap } from '@atlaskit/editor-tables/table-map';
18
+
19
+ import type tablePlugin from '../../plugin';
20
+ import { getPluginState } from '../plugin-factory';
21
+
22
+ import {
23
+ IS_DISABLED_CLASS_NAME,
24
+ SORT_INDEX_DATA_ATTRIBUTE,
25
+ SORTING_ICON_CLASS_NAME,
26
+ } from './consts';
27
+ import { tableViewModeSortPluginKey as key } from './plugin-key';
28
+ import type { ViewModeSortPluginState } from './types';
29
+ import { getTableElements, toggleSort } from './utils';
30
+
31
+ export const createPlugin = (
32
+ editorViewModeAPI: ExtractInjectionAPI<typeof tablePlugin>['editorViewMode'],
33
+ ) => {
34
+ return new SafePlugin({
35
+ state: {
36
+ init: () => ({
37
+ decorations: DecorationSet.empty,
38
+ sort: {},
39
+ allTables: [],
40
+ }),
41
+ apply(tr, pluginState: ViewModeSortPluginState, oldState) {
42
+ // TODO - move this mode check to plugin creation if possible. Right now it's here because the initial state
43
+ // does not appear correct when the plugin is created.
44
+ const { mode } = editorViewModeAPI?.sharedState.currentState() || {};
45
+ if (mode !== 'view') {
46
+ return pluginState;
47
+ }
48
+ let { decorations, sort, allTables } = pluginState;
49
+
50
+ const sortMeta = tr.getMeta('tableSortMeta');
51
+
52
+ let hoverTableMeta = tr.getMeta('mouseEnterTable');
53
+ let removeTableMeta = tr.getMeta('removeTable');
54
+ let tableId = '';
55
+
56
+ // Remove the table from the state
57
+ if (removeTableMeta) {
58
+ allTables = allTables.filter(([id]) => id !== removeTableMeta);
59
+ } else {
60
+ tableId = hoverTableMeta?.[0];
61
+ }
62
+
63
+ sort = { ...sort, ...sortMeta };
64
+
65
+ const isTableInState = allTables.some(([id]) => id === tableId);
66
+
67
+ // Update the table in the state
68
+ if (hoverTableMeta) {
69
+ allTables = allTables.filter(([id]) => id !== hoverTableMeta[0]);
70
+ allTables.push(hoverTableMeta);
71
+ }
72
+
73
+ /**
74
+ * Create decorations for the sorting icons
75
+ */
76
+ const decs: Decoration[] = [];
77
+
78
+ // TODO - add support for keyboard only users
79
+ if ((hoverTableMeta && !isTableInState) || sortMeta) {
80
+ allTables.forEach((table) => {
81
+ const [tableId, _node, pos] = table;
82
+ const tableNode = tr.doc.nodeAt(tr.mapping.map(pos));
83
+ if (!tableNode || tableNode.type.name !== 'table') {
84
+ return pluginState;
85
+ }
86
+ const map = TableMap.get(tableNode);
87
+ const hasMergedCells = new Set(map.map).size !== map.map.length;
88
+ map.mapByRow[0].forEach((cell, index) => {
89
+ // return pluginState;
90
+ decs.push(
91
+ Decoration.widget(cell + pos + 2, () => {
92
+ const element = document.createElement('div');
93
+ element.setAttribute(SORT_INDEX_DATA_ATTRIBUTE, `${index}`);
94
+ element.classList.add(SORTING_ICON_CLASS_NAME);
95
+ if (hasMergedCells) {
96
+ element.classList.add(IS_DISABLED_CLASS_NAME);
97
+ }
98
+
99
+ let sortOrdered;
100
+ if (index === sort[tableId]?.index) {
101
+ sortOrdered = sort[tableId]?.direction;
102
+ } else {
103
+ sortOrdered = SortOrder.NO_ORDER;
104
+ }
105
+
106
+ const { getIntl } = getPluginState(oldState);
107
+
108
+ ReactDOM.render(
109
+ createElement(
110
+ RawIntlProvider,
111
+ { value: getIntl() },
112
+ createElement(SortingIcon, {
113
+ isSortingAllowed: !hasMergedCells,
114
+ sortOrdered,
115
+ onClick: () => {},
116
+ onKeyDown: () => {},
117
+ }),
118
+ ),
119
+ element,
120
+ );
121
+ return element;
122
+ }),
123
+ );
124
+ });
125
+ });
126
+ decorations = DecorationSet.create(tr.doc, decs);
127
+ }
128
+
129
+ /**
130
+ * Map the decorations to the new document if there are changes
131
+ */
132
+ if (tr.docChanged) {
133
+ decorations = decorations.map(tr.mapping, tr.doc);
134
+ allTables = allTables.map((table) => {
135
+ return [table[0], table[1], tr.mapping.map(table[2])];
136
+ });
137
+ }
138
+
139
+ return {
140
+ decorations,
141
+ sort,
142
+ allTables,
143
+ };
144
+ },
145
+ },
146
+ key,
147
+ appendTransaction: (trs, oldState, newState) => {
148
+ // return newState.tr;
149
+ const { mode } = editorViewModeAPI?.sharedState.currentState() || {};
150
+ if (mode !== 'view') {
151
+ return newState.tr;
152
+ }
153
+
154
+ let allTables = key.getState(newState)?.allTables || [];
155
+
156
+ /**
157
+ * If incoming changes have affected a table node, remove the sorting. This prevents the
158
+ * table from breaking if changes like merged cells are incoming.
159
+ */
160
+ for (const tr of trs) {
161
+ const hoverTableMeta = tr.getMeta('mouseEnterTable');
162
+ if (hoverTableMeta) {
163
+ allTables = allTables.filter(([id]) => id !== hoverTableMeta[0]);
164
+ allTables.push(hoverTableMeta);
165
+ }
166
+ const isRemote = tr.getMeta('isRemote');
167
+ const isDocChanged = tr.docChanged;
168
+ const isChangesIncoming = isRemote && isDocChanged;
169
+
170
+ const oldPluginState = key.getState(oldState);
171
+ const newPluginState = key.getState(newState);
172
+
173
+ for (const table of allTables) {
174
+ const [tableId, node, pos] = table;
175
+ const {
176
+ order: oldOrder,
177
+ direction: oldDirection,
178
+ index: oldIndex,
179
+ } = oldPluginState?.sort?.[tableId] || {};
180
+
181
+ if (isChangesIncoming) {
182
+ const maybeTableNode = tr.doc.nodeAt(pos);
183
+ const isTableNodeChanged =
184
+ maybeTableNode?.attrs?.localId !== tableId ||
185
+ !node.eq(maybeTableNode);
186
+
187
+ if (isTableNodeChanged) {
188
+ const newtr = newState.tr;
189
+ newtr.setMeta('tableSortMeta', {
190
+ [tableId]: {},
191
+ });
192
+ newtr.setMeta('removeTable', tableId);
193
+
194
+ // Unsort the table here
195
+ if (oldOrder !== undefined) {
196
+ const { rows, tbody } = getTableElements(tableId);
197
+ if (!rows || !tbody) {
198
+ return newtr;
199
+ }
200
+ const sortedOrder = [...oldOrder].sort(
201
+ (a, b) => a.value - b.value,
202
+ );
203
+ sortedOrder.forEach((index, i) => {
204
+ tbody.appendChild(rows[index.index + 1]);
205
+ });
206
+ return newtr;
207
+ }
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Sort the table if the sort order has changed
213
+ */
214
+ const {
215
+ order: newOrder,
216
+ direction: newDirection,
217
+ index: newIndex,
218
+ } = newPluginState?.sort?.[tableId] || {};
219
+ const orderChanged =
220
+ oldDirection !== newDirection || oldIndex !== newIndex;
221
+
222
+ if (orderChanged) {
223
+ if (!isRemote && newDirection !== SortOrder.NO_ORDER) {
224
+ const { rows, tbody } = getTableElements(tableId);
225
+ if (rows && newOrder) {
226
+ newOrder.forEach((index, i) => {
227
+ tbody?.appendChild(rows[index.value + 1]);
228
+ });
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
234
+ return newState.tr;
235
+ },
236
+ props: {
237
+ handleDOMEvents: {
238
+ keydown: (view, event) => {
239
+ // TODO - fix the focus issue here, where toggling sort with a keypress loses focus
240
+ if (event.key === 'Enter' || event.key === ' ') {
241
+ const pluginState = key.getState(view.state)?.sort || {};
242
+ toggleSort(view, event, pluginState);
243
+ }
244
+ },
245
+
246
+ click: (view, event) => {
247
+ const pluginState = key.getState(view.state)?.sort || {};
248
+ toggleSort(view, event, pluginState);
249
+ },
250
+ },
251
+ decorations(state) {
252
+ const decs = key.getState(state)?.decorations || DecorationSet.empty;
253
+ return decs;
254
+ },
255
+ },
256
+ });
257
+ };
@@ -0,0 +1,6 @@
1
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
2
+
3
+ import type { ViewModeSortPluginState } from './types';
4
+
5
+ export const tableViewModeSortPluginKey =
6
+ new PluginKey<ViewModeSortPluginState>('tableViewModeSortPlugin');
@@ -0,0 +1,23 @@
1
+ import type { SortOrder } from '@atlaskit/editor-common/types';
2
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
+ import type { DecorationSet } from '@atlaskit/editor-prosemirror/view';
4
+
5
+ export type TableSortMeta = Record<
6
+ string,
7
+ {
8
+ index: number;
9
+ order: {
10
+ index: number;
11
+ value: number;
12
+ }[];
13
+ direction: SortOrder;
14
+ }
15
+ >;
16
+
17
+ export interface ViewModeSortPluginState {
18
+ decorations: DecorationSet;
19
+ sort: TableSortMeta;
20
+ allTables: HoverTableMeta[];
21
+ }
22
+
23
+ export type HoverTableMeta = [string, PMNode, number];
@@ -0,0 +1,120 @@
1
+ import { SortOrder } from '@atlaskit/editor-common/types';
2
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
3
+
4
+ import {
5
+ IS_DISABLED_CLASS_NAME,
6
+ SORT_INDEX_DATA_ATTRIBUTE,
7
+ SORTING_ICON_CLASS_NAME,
8
+ } from './consts';
9
+ import type { TableSortMeta } from './types';
10
+
11
+ export const unsort = (
12
+ oldOrder: { index: number; value: number }[],
13
+ tableElement: HTMLElement,
14
+ ) => {
15
+ const rows = tableElement.querySelectorAll('tr');
16
+ const tbody = tableElement.querySelector('tbody');
17
+
18
+ const sortedOrder = [...oldOrder].sort((a, b) => a.value - b.value);
19
+ sortedOrder.forEach((item) => {
20
+ tbody?.appendChild(rows[item.index + 1]);
21
+ });
22
+ };
23
+
24
+ // TODO - reuse sort logic from the Renderer and support switching between ASC, DESC and NO_ORDER
25
+ export const getSortOrderFromTable = (
26
+ tableElement: HTMLElement,
27
+ sortIndex: number,
28
+ direction: string,
29
+ ) => {
30
+ const sortOrder = direction === SortOrder.DESC ? -1 : 1;
31
+ const strings: string[] = [];
32
+ tableElement
33
+ .querySelectorAll('tr:not([data-header-row="true"])')
34
+ .forEach((tr) => {
35
+ strings.push(tr.querySelectorAll('td')[sortIndex]?.textContent || '');
36
+ });
37
+ const order = Array.from(strings.keys())
38
+ .sort((a, b) => {
39
+ const string = strings[a] || '';
40
+ return string.localeCompare(strings[b] || '') * sortOrder;
41
+ })
42
+ .map((value, index) => ({
43
+ value,
44
+ index,
45
+ }));
46
+ // TODO - improve this. right now this is a workaround to ensure the first tr is always first in the order
47
+ return [{ value: -1, index: -1 }, ...order];
48
+ };
49
+
50
+ export const toggleSort = (
51
+ view: EditorView,
52
+ event: Event,
53
+ pluginState: TableSortMeta,
54
+ ) => {
55
+ const target = event.target as HTMLElement;
56
+ const widget = target.closest(`.${SORTING_ICON_CLASS_NAME}`);
57
+ if (widget?.classList.contains(IS_DISABLED_CLASS_NAME) || !widget) {
58
+ return;
59
+ }
60
+ let datasetortIndex = target
61
+ ?.closest('.ProseMirror-widget')
62
+ ?.getAttribute(SORT_INDEX_DATA_ATTRIBUTE);
63
+ const tr = view.state.tr;
64
+ const tableElement = target.closest('table');
65
+ if (!tableElement || !datasetortIndex) {
66
+ return;
67
+ }
68
+ const tableId = tableElement.getAttribute('data-table-local-id') || '';
69
+
70
+ let { index, direction, order: oldOrder } = pluginState?.[tableId] || {};
71
+
72
+ // Unsort if there was already a sort
73
+ if (direction !== SortOrder.NO_ORDER && oldOrder !== undefined) {
74
+ unsort(oldOrder, tableElement);
75
+ }
76
+
77
+ const sortIndex = parseInt(datasetortIndex);
78
+ if (sortIndex === index) {
79
+ switch (direction) {
80
+ case SortOrder.NO_ORDER:
81
+ direction = SortOrder.ASC;
82
+ break;
83
+ case SortOrder.ASC:
84
+ direction = SortOrder.DESC;
85
+ break;
86
+ case SortOrder.DESC:
87
+ direction = SortOrder.NO_ORDER;
88
+ break;
89
+ }
90
+ } else {
91
+ direction = SortOrder.ASC; // default direction when a new index is clicked
92
+ }
93
+
94
+ const order = getSortOrderFromTable(tableElement, sortIndex, direction);
95
+
96
+ if (direction === SortOrder.NO_ORDER) {
97
+ tr.setMeta('tableSortMeta', {
98
+ [tableId]: {},
99
+ });
100
+ } else {
101
+ tr.setMeta('tableSortMeta', {
102
+ [tableId]: {
103
+ index: sortIndex,
104
+ direction,
105
+ order,
106
+ tableElement,
107
+ },
108
+ });
109
+ }
110
+ view.dispatch(tr);
111
+ };
112
+
113
+ export const getTableElements = (tableId: string) => {
114
+ const tableElement = document.querySelector(
115
+ `table[data-table-local-id="${tableId}"]`,
116
+ );
117
+ const tbody = tableElement?.querySelector('tbody');
118
+ const rows = tableElement?.querySelectorAll('tr');
119
+ return { tbody, rows };
120
+ };
@@ -6,6 +6,7 @@ import {
6
6
  tableMarginTop,
7
7
  tableSharedStyle,
8
8
  } from '@atlaskit/editor-common/styles';
9
+ import { SORTABLE_COLUMN_ICON_CLASSNAME } from '@atlaskit/editor-common/table';
9
10
  import type { FeatureFlags } from '@atlaskit/editor-common/types';
10
11
  import { browser } from '@atlaskit/editor-common/utils';
11
12
  import {
@@ -27,6 +28,7 @@ import { N0, N40A, R500 } from '@atlaskit/theme/colors';
27
28
  import { fontSize } from '@atlaskit/theme/constants';
28
29
  import { token } from '@atlaskit/tokens';
29
30
 
31
+ import { SORTING_ICON_CLASS_NAME } from '../pm-plugins/view-mode-sort/consts';
30
32
  import { TableCssClassName as ClassName } from '../types';
31
33
 
32
34
  import {
@@ -203,6 +205,39 @@ const breakoutWidthStyling = () => {
203
205
  `;
204
206
  };
205
207
 
208
+ const viewModeSortStyles = () => {
209
+ if (getBooleanFF('platform.editor.table.live-pages-sorting_4malx')) {
210
+ return css`
211
+ th {
212
+ .${SORTING_ICON_CLASS_NAME} {
213
+ + p {
214
+ margin-top: 0 !important;
215
+ }
216
+ }
217
+
218
+ &:has(.is-active) {
219
+ .${SORTABLE_COLUMN_ICON_CLASSNAME} {
220
+ opacity: 1;
221
+ }
222
+ }
223
+
224
+ .${SORTABLE_COLUMN_ICON_CLASSNAME} {
225
+ opacity: 0;
226
+ &:focus {
227
+ opacity: 1;
228
+ }
229
+ }
230
+
231
+ &:hover {
232
+ .${SORTABLE_COLUMN_ICON_CLASSNAME} {
233
+ opacity: 1;
234
+ }
235
+ }
236
+ }
237
+ `;
238
+ }
239
+ };
240
+
206
241
  const tableBorderStyles = () => {
207
242
  if (getBooleanFF('platform.editor.table.column-controls-styles-updated')) {
208
243
  return `border-color: ${tableBorderDeleteColor}`;
@@ -269,6 +304,7 @@ export const baseTableStyles = (props: { featureFlags?: FeatureFlags }) => css`
269
304
  ${props.featureFlags?.tableDragAndDrop && insertLine()};
270
305
  ${resizeHandle(props.featureFlags?.tableDragAndDrop)};
271
306
  ${rangeSelectionStyles};
307
+ ${viewModeSortStyles()};
272
308
 
273
309
  .${ClassName.LAST_ITEM_IN_CELL} {
274
310
  margin-bottom: 0;