@atlaskit/editor-plugin-placeholder-text 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/.eslintrc.js +14 -0
- package/CHANGELOG.md +1 -0
- package/LICENSE.md +13 -0
- package/README.md +30 -0
- package/dist/cjs/actions.js +35 -0
- package/dist/cjs/fake-text-cursor/cursor.js +107 -0
- package/dist/cjs/index.js +13 -0
- package/dist/cjs/placeholder-text-nodeview.js +89 -0
- package/dist/cjs/plugin-key.js +8 -0
- package/dist/cjs/plugin.js +270 -0
- package/dist/cjs/selection-utils.js +14 -0
- package/dist/cjs/styles.js +13 -0
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/ui/FloatingToolbar/index.js +88 -0
- package/dist/cjs/ui/FloatingToolbar/styles.js +15 -0
- package/dist/cjs/ui/FloatingToolbar/utils.js +55 -0
- package/dist/cjs/ui/PlaceholderFloatingToolbar/index.js +90 -0
- package/dist/es2019/actions.js +27 -0
- package/dist/es2019/fake-text-cursor/cursor.js +77 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/placeholder-text-nodeview.js +77 -0
- package/dist/es2019/plugin-key.js +2 -0
- package/dist/es2019/plugin.js +250 -0
- package/dist/es2019/selection-utils.js +8 -0
- package/dist/es2019/styles.js +52 -0
- package/dist/es2019/types.js +1 -0
- package/dist/es2019/ui/FloatingToolbar/index.js +49 -0
- package/dist/es2019/ui/FloatingToolbar/styles.js +15 -0
- package/dist/es2019/ui/FloatingToolbar/utils.js +42 -0
- package/dist/es2019/ui/PlaceholderFloatingToolbar/index.js +61 -0
- package/dist/esm/actions.js +29 -0
- package/dist/esm/fake-text-cursor/cursor.js +100 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/placeholder-text-nodeview.js +82 -0
- package/dist/esm/plugin-key.js +2 -0
- package/dist/esm/plugin.js +262 -0
- package/dist/esm/selection-utils.js +8 -0
- package/dist/esm/styles.js +6 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/ui/FloatingToolbar/index.js +67 -0
- package/dist/esm/ui/FloatingToolbar/styles.js +8 -0
- package/dist/esm/ui/FloatingToolbar/utils.js +49 -0
- package/dist/esm/ui/PlaceholderFloatingToolbar/index.js +80 -0
- package/dist/types/actions.d.ts +4 -0
- package/dist/types/fake-text-cursor/cursor.d.ts +30 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/placeholder-text-nodeview.d.ts +17 -0
- package/dist/types/plugin-key.d.ts +3 -0
- package/dist/types/plugin.d.ts +7 -0
- package/dist/types/selection-utils.d.ts +2 -0
- package/dist/types/styles.d.ts +1 -0
- package/dist/types/types.d.ts +22 -0
- package/dist/types/ui/FloatingToolbar/index.d.ts +28 -0
- package/dist/types/ui/FloatingToolbar/styles.d.ts +1 -0
- package/dist/types/ui/FloatingToolbar/utils.d.ts +18 -0
- package/dist/types/ui/PlaceholderFloatingToolbar/index.d.ts +25 -0
- package/dist/types-ts4.5/actions.d.ts +4 -0
- package/dist/types-ts4.5/fake-text-cursor/cursor.d.ts +30 -0
- package/dist/types-ts4.5/index.d.ts +2 -0
- package/dist/types-ts4.5/placeholder-text-nodeview.d.ts +17 -0
- package/dist/types-ts4.5/plugin-key.d.ts +3 -0
- package/dist/types-ts4.5/plugin.d.ts +7 -0
- package/dist/types-ts4.5/selection-utils.d.ts +2 -0
- package/dist/types-ts4.5/styles.d.ts +1 -0
- package/dist/types-ts4.5/types.d.ts +22 -0
- package/dist/types-ts4.5/ui/FloatingToolbar/index.d.ts +28 -0
- package/dist/types-ts4.5/ui/FloatingToolbar/styles.d.ts +1 -0
- package/dist/types-ts4.5/ui/FloatingToolbar/utils.d.ts +18 -0
- package/dist/types-ts4.5/ui/PlaceholderFloatingToolbar/index.d.ts +25 -0
- package/package.json +100 -0
- package/report.api.md +80 -0
- package/styles/package.json +15 -0
- package/tmp/api-report-tmp.d.ts +49 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { placeholder } from '@atlaskit/adf-schema';
|
|
3
|
+
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
4
|
+
import { useSharedPluginState } from '@atlaskit/editor-common/hooks';
|
|
5
|
+
import { toolbarInsertBlockMessages as messages } from '@atlaskit/editor-common/messages';
|
|
6
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
7
|
+
import { isNodeEmpty } from '@atlaskit/editor-common/utils';
|
|
8
|
+
import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
9
|
+
import MediaServicesTextIcon from '@atlaskit/icon/glyph/media-services/text';
|
|
10
|
+
import { hidePlaceholderFloatingToolbar, insertPlaceholderTextAtSelection, showPlaceholderFloatingToolbar } from './actions';
|
|
11
|
+
import { drawFakeTextCursor, FakeTextCursorSelection } from './fake-text-cursor/cursor';
|
|
12
|
+
import { PlaceholderTextNodeView } from './placeholder-text-nodeview';
|
|
13
|
+
import { pluginKey } from './plugin-key';
|
|
14
|
+
import { isSelectionAtPlaceholder } from './selection-utils';
|
|
15
|
+
import PlaceholderFloatingToolbar from './ui/PlaceholderFloatingToolbar';
|
|
16
|
+
const getOpenTypeAhead = (trigger, api) => {
|
|
17
|
+
var _api$typeAhead, _api$typeAhead$action, _api$typeAhead2, _api$typeAhead2$actio;
|
|
18
|
+
const typeAheadHandler = api === null || api === void 0 ? void 0 : (_api$typeAhead = api.typeAhead) === null || _api$typeAhead === void 0 ? void 0 : (_api$typeAhead$action = _api$typeAhead.actions) === null || _api$typeAhead$action === void 0 ? void 0 : _api$typeAhead$action.findHandlerByTrigger(trigger);
|
|
19
|
+
if (!typeAheadHandler || !typeAheadHandler.id) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return api === null || api === void 0 ? void 0 : (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : (_api$typeAhead2$actio = _api$typeAhead2.actions) === null || _api$typeAhead2$actio === void 0 ? void 0 : _api$typeAhead2$actio.openAtTransaction({
|
|
23
|
+
triggerHandler: typeAheadHandler,
|
|
24
|
+
inputMethod: INPUT_METHOD.KEYBOARD
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
export function createPlugin(dispatch, options, api) {
|
|
28
|
+
const allowInserting = !!options.allowInserting;
|
|
29
|
+
return new SafePlugin({
|
|
30
|
+
key: pluginKey,
|
|
31
|
+
state: {
|
|
32
|
+
init: () => ({
|
|
33
|
+
showInsertPanelAt: null,
|
|
34
|
+
allowInserting
|
|
35
|
+
}),
|
|
36
|
+
apply: (tr, state) => {
|
|
37
|
+
const meta = tr.getMeta(pluginKey);
|
|
38
|
+
if (meta && meta.showInsertPanelAt !== undefined) {
|
|
39
|
+
const newState = {
|
|
40
|
+
showInsertPanelAt: meta.showInsertPanelAt,
|
|
41
|
+
allowInserting
|
|
42
|
+
};
|
|
43
|
+
dispatch(pluginKey, newState);
|
|
44
|
+
return newState;
|
|
45
|
+
} else if (state.showInsertPanelAt) {
|
|
46
|
+
const newState = {
|
|
47
|
+
showInsertPanelAt: tr.mapping.map(state.showInsertPanelAt),
|
|
48
|
+
allowInserting
|
|
49
|
+
};
|
|
50
|
+
dispatch(pluginKey, newState);
|
|
51
|
+
return newState;
|
|
52
|
+
}
|
|
53
|
+
return state;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
appendTransaction(transactions, oldState, newState) {
|
|
57
|
+
if (transactions.some(txn => txn.docChanged)) {
|
|
58
|
+
const didPlaceholderExistBeforeTxn = oldState.selection.$head.nodeAfter === newState.selection.$head.nodeAfter;
|
|
59
|
+
const adjacentNode = newState.selection.$head.nodeAfter;
|
|
60
|
+
const adjacentNodePos = newState.selection.$head.pos;
|
|
61
|
+
const placeholderNodeType = newState.schema.nodes.placeholder;
|
|
62
|
+
if (adjacentNode && adjacentNode.type === placeholderNodeType && didPlaceholderExistBeforeTxn) {
|
|
63
|
+
var _$newHead$nodeBefore;
|
|
64
|
+
const {
|
|
65
|
+
$head: $newHead
|
|
66
|
+
} = newState.selection;
|
|
67
|
+
const {
|
|
68
|
+
$head: $oldHead
|
|
69
|
+
} = oldState.selection;
|
|
70
|
+
// Check that cursor has moved forward in the document **and** that there is content before the cursor
|
|
71
|
+
const cursorMoved = $oldHead.pos < $newHead.pos;
|
|
72
|
+
const nodeBeforeHasContent = !isNodeEmpty($newHead.nodeBefore);
|
|
73
|
+
const nodeBeforeIsInline = (_$newHead$nodeBefore = $newHead.nodeBefore) === null || _$newHead$nodeBefore === void 0 ? void 0 : _$newHead$nodeBefore.type.isInline;
|
|
74
|
+
if (cursorMoved && (nodeBeforeHasContent || nodeBeforeIsInline)) {
|
|
75
|
+
const {
|
|
76
|
+
$from,
|
|
77
|
+
$to
|
|
78
|
+
} = NodeSelection.create(newState.doc, adjacentNodePos);
|
|
79
|
+
return newState.tr.deleteRange($from.pos, $to.pos);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Handle Fake Text Cursor for Floating Toolbar
|
|
85
|
+
if (!pluginKey.getState(oldState).showInsertPanelAt && pluginKey.getState(newState).showInsertPanelAt) {
|
|
86
|
+
return newState.tr.setSelection(new FakeTextCursorSelection(newState.selection.$from));
|
|
87
|
+
}
|
|
88
|
+
if (pluginKey.getState(oldState).showInsertPanelAt && !pluginKey.getState(newState).showInsertPanelAt) {
|
|
89
|
+
if (newState.selection instanceof FakeTextCursorSelection) {
|
|
90
|
+
return newState.tr.setSelection(new TextSelection(newState.selection.$from));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
},
|
|
95
|
+
props: {
|
|
96
|
+
decorations: drawFakeTextCursor,
|
|
97
|
+
handleDOMEvents: {
|
|
98
|
+
beforeinput: (view, event) => {
|
|
99
|
+
const {
|
|
100
|
+
state
|
|
101
|
+
} = view;
|
|
102
|
+
if (event instanceof InputEvent && !event.isComposing && event.inputType === 'insertText' && isSelectionAtPlaceholder(view.state.selection)) {
|
|
103
|
+
event.stopPropagation();
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
const startNodePosition = state.selection.from;
|
|
106
|
+
const content = event.data || '';
|
|
107
|
+
const tr = view.state.tr;
|
|
108
|
+
tr.delete(startNodePosition, startNodePosition + 1);
|
|
109
|
+
const openTypeAhead = getOpenTypeAhead(content, api);
|
|
110
|
+
if (openTypeAhead) {
|
|
111
|
+
openTypeAhead(tr);
|
|
112
|
+
} else {
|
|
113
|
+
tr.insertText(content);
|
|
114
|
+
}
|
|
115
|
+
view.dispatch(tr);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
nodeViews: {
|
|
122
|
+
placeholder: (node, view, getPos) => new PlaceholderTextNodeView(node, view, getPos)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function ContentComponent({
|
|
128
|
+
editorView,
|
|
129
|
+
dependencyApi,
|
|
130
|
+
popupsMountPoint,
|
|
131
|
+
popupsBoundariesElement
|
|
132
|
+
}) {
|
|
133
|
+
const {
|
|
134
|
+
placeholderTextState
|
|
135
|
+
} = useSharedPluginState(dependencyApi, ['placeholderText']);
|
|
136
|
+
const insertPlaceholderText = value => insertPlaceholderTextAtSelection(value)(editorView.state, editorView.dispatch);
|
|
137
|
+
const hidePlaceholderToolbar = () => hidePlaceholderFloatingToolbar(editorView.state, editorView.dispatch);
|
|
138
|
+
const getNodeFromPos = pos => editorView.domAtPos(pos).node;
|
|
139
|
+
const getFixedCoordinatesFromPos = pos => editorView.coordsAtPos(pos);
|
|
140
|
+
const setFocusInEditor = () => editorView.focus();
|
|
141
|
+
if (placeholderTextState !== null && placeholderTextState !== void 0 && placeholderTextState.showInsertPanelAt) {
|
|
142
|
+
return /*#__PURE__*/React.createElement(PlaceholderFloatingToolbar, {
|
|
143
|
+
editorViewDOM: editorView.dom,
|
|
144
|
+
popupsMountPoint: popupsMountPoint,
|
|
145
|
+
popupsBoundariesElement: popupsBoundariesElement,
|
|
146
|
+
getFixedCoordinatesFromPos: getFixedCoordinatesFromPos,
|
|
147
|
+
getNodeFromPos: getNodeFromPos,
|
|
148
|
+
hidePlaceholderFloatingToolbar: hidePlaceholderToolbar,
|
|
149
|
+
showInsertPanelAt: placeholderTextState.showInsertPanelAt,
|
|
150
|
+
insertPlaceholder: insertPlaceholderText,
|
|
151
|
+
setFocusInEditor: setFocusInEditor
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const basePlaceholderTextPlugin = ({
|
|
157
|
+
api,
|
|
158
|
+
config: options
|
|
159
|
+
}) => ({
|
|
160
|
+
name: 'placeholderText',
|
|
161
|
+
nodes() {
|
|
162
|
+
return [{
|
|
163
|
+
name: 'placeholder',
|
|
164
|
+
node: placeholder
|
|
165
|
+
}];
|
|
166
|
+
},
|
|
167
|
+
pmPlugins() {
|
|
168
|
+
return [{
|
|
169
|
+
name: 'placeholderText',
|
|
170
|
+
plugin: ({
|
|
171
|
+
dispatch
|
|
172
|
+
}) => createPlugin(dispatch, options, api)
|
|
173
|
+
}];
|
|
174
|
+
},
|
|
175
|
+
actions: {
|
|
176
|
+
showPlaceholderFloatingToolbar
|
|
177
|
+
},
|
|
178
|
+
getSharedState(editorState) {
|
|
179
|
+
if (!editorState) {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
const {
|
|
183
|
+
showInsertPanelAt,
|
|
184
|
+
allowInserting
|
|
185
|
+
} = pluginKey.getState(editorState) || {
|
|
186
|
+
showInsertPanelAt: null
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
showInsertPanelAt,
|
|
190
|
+
allowInserting: !!allowInserting
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
contentComponent({
|
|
194
|
+
editorView,
|
|
195
|
+
popupsMountPoint,
|
|
196
|
+
popupsBoundariesElement
|
|
197
|
+
}) {
|
|
198
|
+
return /*#__PURE__*/React.createElement(ContentComponent, {
|
|
199
|
+
editorView: editorView,
|
|
200
|
+
popupsMountPoint: popupsMountPoint,
|
|
201
|
+
popupsBoundariesElement: popupsBoundariesElement,
|
|
202
|
+
dependencyApi: api
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
const decorateWithPluginOptions = (plugin, options, api) => {
|
|
207
|
+
if (!options.allowInserting) {
|
|
208
|
+
return plugin;
|
|
209
|
+
}
|
|
210
|
+
plugin.pluginsOptions = {
|
|
211
|
+
quickInsert: ({
|
|
212
|
+
formatMessage
|
|
213
|
+
}) => [{
|
|
214
|
+
id: 'placeholderText',
|
|
215
|
+
title: formatMessage(messages.placeholderText),
|
|
216
|
+
description: formatMessage(messages.placeholderTextDescription),
|
|
217
|
+
priority: 1400,
|
|
218
|
+
keywords: ['placeholder'],
|
|
219
|
+
icon: () => /*#__PURE__*/React.createElement(MediaServicesTextIcon, {
|
|
220
|
+
label: ""
|
|
221
|
+
}),
|
|
222
|
+
action(insert, state) {
|
|
223
|
+
var _api$analytics;
|
|
224
|
+
const tr = state.tr;
|
|
225
|
+
tr.setMeta(pluginKey, {
|
|
226
|
+
showInsertPanelAt: tr.selection.anchor
|
|
227
|
+
});
|
|
228
|
+
api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.attachAnalyticsEvent({
|
|
229
|
+
action: ACTION.INSERTED,
|
|
230
|
+
actionSubject: ACTION_SUBJECT.DOCUMENT,
|
|
231
|
+
actionSubjectId: ACTION_SUBJECT_ID.PLACEHOLDER_TEXT,
|
|
232
|
+
attributes: {
|
|
233
|
+
inputMethod: INPUT_METHOD.QUICK_INSERT
|
|
234
|
+
},
|
|
235
|
+
eventType: EVENT_TYPE.TRACK
|
|
236
|
+
})(tr);
|
|
237
|
+
return tr;
|
|
238
|
+
}
|
|
239
|
+
}]
|
|
240
|
+
};
|
|
241
|
+
return plugin;
|
|
242
|
+
};
|
|
243
|
+
const placeholderTextPlugin = ({
|
|
244
|
+
config: options = {},
|
|
245
|
+
api
|
|
246
|
+
}) => decorateWithPluginOptions(basePlaceholderTextPlugin({
|
|
247
|
+
config: options,
|
|
248
|
+
api
|
|
249
|
+
}), options, api);
|
|
250
|
+
export default placeholderTextPlugin;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
2
|
+
export const isSelectionAtPlaceholder = selection => {
|
|
3
|
+
if (!(selection instanceof TextSelection) || !selection.$cursor) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
const node = selection.$cursor.doc.nodeAt(selection.$cursor.pos);
|
|
7
|
+
return (node === null || node === void 0 ? void 0 : node.type.name) === 'placeholder';
|
|
8
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { css } from '@emotion/react';
|
|
2
|
+
import { akEditorSelectedNodeClassName, getSelectionStyles, SelectionStyle } from '@atlaskit/editor-shared-styles';
|
|
3
|
+
import { B75, N200 } from '@atlaskit/theme/colors';
|
|
4
|
+
export const placeholderTextStyles = css`
|
|
5
|
+
.ProseMirror span[data-placeholder] {
|
|
6
|
+
color: ${`var(--ds-text-subtlest, ${N200})`};
|
|
7
|
+
display: inline;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.ProseMirror span.pm-placeholder {
|
|
11
|
+
display: inline;
|
|
12
|
+
color: ${`var(--ds-text-subtlest, ${N200})`};
|
|
13
|
+
}
|
|
14
|
+
.ProseMirror span.pm-placeholder__text {
|
|
15
|
+
display: inline;
|
|
16
|
+
color: ${`var(--ds-text-subtlest, ${N200})`};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ProseMirror span.pm-placeholder.${akEditorSelectedNodeClassName} {
|
|
20
|
+
${getSelectionStyles([SelectionStyle.Background])}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.ProseMirror span.pm-placeholder__text[data-placeholder]::after {
|
|
24
|
+
color: ${`var(--ds-text-subtlest, ${N200})`};
|
|
25
|
+
cursor: text;
|
|
26
|
+
content: attr(data-placeholder);
|
|
27
|
+
display: inline;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.ProseMirror {
|
|
31
|
+
.ProseMirror-fake-text-cursor {
|
|
32
|
+
display: inline;
|
|
33
|
+
pointer-events: none;
|
|
34
|
+
position: relative;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.ProseMirror-fake-text-cursor::after {
|
|
38
|
+
content: '';
|
|
39
|
+
display: inline;
|
|
40
|
+
top: 0;
|
|
41
|
+
position: absolute;
|
|
42
|
+
border-right: 1px solid ${"var(--ds-border, rgba(0, 0, 0, 0.4))"};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.ProseMirror-fake-text-selection {
|
|
46
|
+
display: inline;
|
|
47
|
+
pointer-events: none;
|
|
48
|
+
position: relative;
|
|
49
|
+
background-color: ${`var(--ds-background-selected, ${B75})`};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* eslint-disable @atlaskit/design-system/prefer-primitives */
|
|
2
|
+
/** @jsx jsx */
|
|
3
|
+
import { PureComponent } from 'react';
|
|
4
|
+
import { jsx } from '@emotion/react';
|
|
5
|
+
import { Popup } from '@atlaskit/editor-common/ui';
|
|
6
|
+
import { container } from './styles';
|
|
7
|
+
export { handlePositionCalculatedWith, getOffsetParent, getNearestNonTextNode } from './utils';
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line @repo/internal/react/no-class-components
|
|
10
|
+
export default class FloatingToolbar extends PureComponent {
|
|
11
|
+
render() {
|
|
12
|
+
const {
|
|
13
|
+
children,
|
|
14
|
+
target,
|
|
15
|
+
offset,
|
|
16
|
+
fitWidth,
|
|
17
|
+
fitHeight = 40,
|
|
18
|
+
onPositionCalculated,
|
|
19
|
+
popupsMountPoint,
|
|
20
|
+
popupsBoundariesElement,
|
|
21
|
+
className,
|
|
22
|
+
absoluteOffset,
|
|
23
|
+
alignX,
|
|
24
|
+
alignY,
|
|
25
|
+
zIndex
|
|
26
|
+
} = this.props;
|
|
27
|
+
if (!target) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return jsx(Popup, {
|
|
31
|
+
absoluteOffset: absoluteOffset,
|
|
32
|
+
alignX: alignX,
|
|
33
|
+
alignY: alignY,
|
|
34
|
+
target: target,
|
|
35
|
+
zIndex: zIndex,
|
|
36
|
+
mountTo: popupsMountPoint,
|
|
37
|
+
boundariesElement: popupsBoundariesElement,
|
|
38
|
+
offset: offset,
|
|
39
|
+
fitWidth: fitWidth,
|
|
40
|
+
fitHeight: fitHeight,
|
|
41
|
+
onPositionCalculated: onPositionCalculated
|
|
42
|
+
}, jsx("div", {
|
|
43
|
+
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
|
|
44
|
+
css: container(fitHeight),
|
|
45
|
+
"data-testid": "popup-container",
|
|
46
|
+
className: className
|
|
47
|
+
}, children));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { css } from '@emotion/react';
|
|
2
|
+
import { N0, N50A, N60A } from '@atlaskit/theme/colors';
|
|
3
|
+
import { borderRadius } from '@atlaskit/theme/constants';
|
|
4
|
+
export const container = height => css`
|
|
5
|
+
border-radius: ${borderRadius()}px;
|
|
6
|
+
box-shadow: ${`var(--ds-shadow-overlay, ${`0 12px 24px -6px ${N50A}, 0 0 1px ${N60A}`})`};
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
padding: ${"var(--ds-space-050, 4px)"} ${"var(--ds-space-100, 8px)"};
|
|
11
|
+
background-color: ${`var(--ds-background-input, ${N0})`};
|
|
12
|
+
${height ? css`
|
|
13
|
+
height: ${height}px;
|
|
14
|
+
` : ''};
|
|
15
|
+
`;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const getCursorHeightFrom = node => parseFloat(window.getComputedStyle(node, undefined).lineHeight || '');
|
|
2
|
+
export const getOffsetParent = (editorViewDom, popupsMountPoint) => popupsMountPoint ? popupsMountPoint.offsetParent : editorViewDom.offsetParent;
|
|
3
|
+
export const getNearestNonTextNode = node => node.nodeType === Node.TEXT_NODE ? node.parentNode : node;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* We need to translate the co-ordinates because `coordsAtPos` returns co-ordinates
|
|
7
|
+
* relative to `window`. And, also need to adjust the cursor container height.
|
|
8
|
+
* (0, 0)
|
|
9
|
+
* +--------------------- [window] ---------------------+
|
|
10
|
+
* | (left, top) +-------- [Offset Parent] --------+ |
|
|
11
|
+
* | {coordsAtPos} | [Cursor] <- cursorHeight | |
|
|
12
|
+
* | | [FloatingToolbar] | |
|
|
13
|
+
*/
|
|
14
|
+
const convertFixedCoordinatesToAbsolutePositioning = (coordinates, offsetParent, cursorHeight) => {
|
|
15
|
+
var _coordinates$left, _coordinates$right, _coordinates$top, _coordinates$top2;
|
|
16
|
+
const {
|
|
17
|
+
left: offsetParentLeft,
|
|
18
|
+
top: offsetParentTop,
|
|
19
|
+
height: offsetParentHeight
|
|
20
|
+
} = offsetParent.getBoundingClientRect();
|
|
21
|
+
return {
|
|
22
|
+
left: ((_coordinates$left = coordinates.left) !== null && _coordinates$left !== void 0 ? _coordinates$left : 0) - offsetParentLeft,
|
|
23
|
+
right: ((_coordinates$right = coordinates.right) !== null && _coordinates$right !== void 0 ? _coordinates$right : 0) - offsetParentLeft,
|
|
24
|
+
top: ((_coordinates$top = coordinates.top) !== null && _coordinates$top !== void 0 ? _coordinates$top : 0) - (offsetParentTop - cursorHeight) + offsetParent.scrollTop,
|
|
25
|
+
bottom: offsetParentHeight - (((_coordinates$top2 = coordinates.top) !== null && _coordinates$top2 !== void 0 ? _coordinates$top2 : 0) - (offsetParentTop - cursorHeight) - offsetParent.scrollTop)
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export const handlePositionCalculatedWith = (offsetParent, node, getCurrentFixedCoordinates) => position => {
|
|
29
|
+
if (!offsetParent) {
|
|
30
|
+
return position;
|
|
31
|
+
}
|
|
32
|
+
const target = getNearestNonTextNode(node);
|
|
33
|
+
const cursorHeight = getCursorHeightFrom(target);
|
|
34
|
+
const fixedCoordinates = getCurrentFixedCoordinates();
|
|
35
|
+
const absoluteCoordinates = convertFixedCoordinatesToAbsolutePositioning(fixedCoordinates, offsetParent, cursorHeight);
|
|
36
|
+
return {
|
|
37
|
+
left: position.left ? absoluteCoordinates.left : undefined,
|
|
38
|
+
right: position.right ? absoluteCoordinates.right : undefined,
|
|
39
|
+
top: position.top ? absoluteCoordinates.top : undefined,
|
|
40
|
+
bottom: position.bottom ? absoluteCoordinates.bottom : undefined
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { defineMessages, injectIntl } from 'react-intl-next';
|
|
4
|
+
import { PanelTextInput } from '@atlaskit/editor-common/ui';
|
|
5
|
+
import FloatingToolbar, { getNearestNonTextNode, getOffsetParent, handlePositionCalculatedWith } from '../FloatingToolbar';
|
|
6
|
+
export const messages = defineMessages({
|
|
7
|
+
placeholderTextPlaceholder: {
|
|
8
|
+
id: 'fabric.editor.placeholderTextPlaceholder',
|
|
9
|
+
defaultMessage: 'Add placeholder text',
|
|
10
|
+
description: ''
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
// eslint-disable-next-line @repo/internal/react/no-class-components
|
|
14
|
+
class PlaceholderFloatingToolbar extends React.Component {
|
|
15
|
+
constructor(...args) {
|
|
16
|
+
super(...args);
|
|
17
|
+
_defineProperty(this, "handleSubmit", value => {
|
|
18
|
+
if (value) {
|
|
19
|
+
this.props.insertPlaceholder(value);
|
|
20
|
+
this.props.setFocusInEditor();
|
|
21
|
+
} else {
|
|
22
|
+
this.props.hidePlaceholderFloatingToolbar();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
_defineProperty(this, "handleBlur", () => {
|
|
26
|
+
this.props.hidePlaceholderFloatingToolbar();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
render() {
|
|
30
|
+
const {
|
|
31
|
+
getNodeFromPos,
|
|
32
|
+
showInsertPanelAt,
|
|
33
|
+
editorViewDOM,
|
|
34
|
+
popupsMountPoint,
|
|
35
|
+
getFixedCoordinatesFromPos,
|
|
36
|
+
popupsBoundariesElement,
|
|
37
|
+
intl: {
|
|
38
|
+
formatMessage
|
|
39
|
+
}
|
|
40
|
+
} = this.props;
|
|
41
|
+
const target = getNodeFromPos(showInsertPanelAt);
|
|
42
|
+
const offsetParent = getOffsetParent(editorViewDOM, popupsMountPoint);
|
|
43
|
+
const getFixedCoordinates = () => getFixedCoordinatesFromPos(showInsertPanelAt);
|
|
44
|
+
const handlePositionCalculated = handlePositionCalculatedWith(offsetParent, target, getFixedCoordinates);
|
|
45
|
+
return /*#__PURE__*/React.createElement(FloatingToolbar, {
|
|
46
|
+
target: getNearestNonTextNode(target),
|
|
47
|
+
onPositionCalculated: handlePositionCalculated,
|
|
48
|
+
popupsMountPoint: popupsMountPoint,
|
|
49
|
+
popupsBoundariesElement: popupsBoundariesElement,
|
|
50
|
+
fitHeight: 32,
|
|
51
|
+
offset: [0, 12]
|
|
52
|
+
}, /*#__PURE__*/React.createElement(PanelTextInput, {
|
|
53
|
+
placeholder: formatMessage(messages.placeholderTextPlaceholder),
|
|
54
|
+
onSubmit: this.handleSubmit,
|
|
55
|
+
onBlur: this.handleBlur,
|
|
56
|
+
autoFocus: true,
|
|
57
|
+
width: 300
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export default injectIntl(PlaceholderFloatingToolbar);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { pluginKey } from './plugin-key';
|
|
2
|
+
export var showPlaceholderFloatingToolbar = function showPlaceholderFloatingToolbar(state, dispatch) {
|
|
3
|
+
var tr = state.tr;
|
|
4
|
+
if (!state.selection.empty) {
|
|
5
|
+
tr.deleteSelection();
|
|
6
|
+
}
|
|
7
|
+
tr.setMeta(pluginKey, {
|
|
8
|
+
showInsertPanelAt: tr.selection.anchor
|
|
9
|
+
});
|
|
10
|
+
tr.scrollIntoView();
|
|
11
|
+
dispatch(tr);
|
|
12
|
+
return true;
|
|
13
|
+
};
|
|
14
|
+
export var insertPlaceholderTextAtSelection = function insertPlaceholderTextAtSelection(value) {
|
|
15
|
+
return function (state, dispatch) {
|
|
16
|
+
dispatch(state.tr.replaceSelectionWith(state.schema.nodes.placeholder.createChecked({
|
|
17
|
+
text: value
|
|
18
|
+
})).setMeta(pluginKey, {
|
|
19
|
+
showInsertPanelAt: null
|
|
20
|
+
}).scrollIntoView());
|
|
21
|
+
return true;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export var hidePlaceholderFloatingToolbar = function hidePlaceholderFloatingToolbar(state, dispatch) {
|
|
25
|
+
dispatch(state.tr.setMeta(pluginKey, {
|
|
26
|
+
showInsertPanelAt: null
|
|
27
|
+
}));
|
|
28
|
+
return true;
|
|
29
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import _inherits from "@babel/runtime/helpers/inherits";
|
|
2
|
+
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
|
|
3
|
+
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
|
|
4
|
+
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
5
|
+
import _createClass from "@babel/runtime/helpers/createClass";
|
|
6
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
7
|
+
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
|
|
8
|
+
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
|
|
9
|
+
import { Slice } from '@atlaskit/editor-prosemirror/model';
|
|
10
|
+
import { Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
11
|
+
import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
12
|
+
export var FakeTextCursorBookmark = /*#__PURE__*/function () {
|
|
13
|
+
function FakeTextCursorBookmark(pos) {
|
|
14
|
+
_classCallCheck(this, FakeTextCursorBookmark);
|
|
15
|
+
_defineProperty(this, "pos", undefined);
|
|
16
|
+
_defineProperty(this, "visible", false);
|
|
17
|
+
this.pos = pos;
|
|
18
|
+
}
|
|
19
|
+
_createClass(FakeTextCursorBookmark, [{
|
|
20
|
+
key: "map",
|
|
21
|
+
value: function map(mapping) {
|
|
22
|
+
return new FakeTextCursorBookmark(mapping.map(this.pos));
|
|
23
|
+
}
|
|
24
|
+
}, {
|
|
25
|
+
key: "resolve",
|
|
26
|
+
value: function resolve(doc) {
|
|
27
|
+
var $pos = doc.resolve(this.pos);
|
|
28
|
+
return Selection.near($pos);
|
|
29
|
+
}
|
|
30
|
+
}]);
|
|
31
|
+
return FakeTextCursorBookmark;
|
|
32
|
+
}();
|
|
33
|
+
export var FakeTextCursorSelection = /*#__PURE__*/function (_Selection) {
|
|
34
|
+
_inherits(FakeTextCursorSelection, _Selection);
|
|
35
|
+
var _super = _createSuper(FakeTextCursorSelection);
|
|
36
|
+
function FakeTextCursorSelection($pos) {
|
|
37
|
+
_classCallCheck(this, FakeTextCursorSelection);
|
|
38
|
+
return _super.call(this, $pos, $pos);
|
|
39
|
+
}
|
|
40
|
+
_createClass(FakeTextCursorSelection, [{
|
|
41
|
+
key: "map",
|
|
42
|
+
value: function map(doc, mapping) {
|
|
43
|
+
var $pos = doc.resolve(mapping.map(this.$head.pos));
|
|
44
|
+
return new FakeTextCursorSelection($pos);
|
|
45
|
+
}
|
|
46
|
+
}, {
|
|
47
|
+
key: "eq",
|
|
48
|
+
value: function eq(other) {
|
|
49
|
+
return other instanceof FakeTextCursorSelection && other.head === this.head;
|
|
50
|
+
}
|
|
51
|
+
}, {
|
|
52
|
+
key: "toJSON",
|
|
53
|
+
value: function toJSON() {
|
|
54
|
+
return {
|
|
55
|
+
type: 'Cursor',
|
|
56
|
+
pos: this.head
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}, {
|
|
60
|
+
key: "getBookmark",
|
|
61
|
+
value: function getBookmark() {
|
|
62
|
+
return new FakeTextCursorBookmark(this.anchor);
|
|
63
|
+
}
|
|
64
|
+
}], [{
|
|
65
|
+
key: "content",
|
|
66
|
+
value: function content() {
|
|
67
|
+
return Slice.empty;
|
|
68
|
+
}
|
|
69
|
+
}, {
|
|
70
|
+
key: "fromJSON",
|
|
71
|
+
value: function fromJSON(doc, json) {
|
|
72
|
+
return new FakeTextCursorSelection(doc.resolve(json.pos));
|
|
73
|
+
}
|
|
74
|
+
}]);
|
|
75
|
+
return FakeTextCursorSelection;
|
|
76
|
+
}(Selection);
|
|
77
|
+
Selection.jsonID('fake-text-cursor', FakeTextCursorSelection);
|
|
78
|
+
export var addFakeTextCursor = function addFakeTextCursor(state, dispatch) {
|
|
79
|
+
var selection = state.selection;
|
|
80
|
+
if (selection.empty) {
|
|
81
|
+
var $from = state.selection.$from;
|
|
82
|
+
dispatch(state.tr.setSelection(new FakeTextCursorSelection($from)));
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
export var removeFakeTextCursor = function removeFakeTextCursor(state, dispatch) {
|
|
86
|
+
if (state.selection instanceof FakeTextCursorSelection) {
|
|
87
|
+
var $from = state.selection.$from;
|
|
88
|
+
dispatch(state.tr.setSelection(new TextSelection($from)));
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
export var drawFakeTextCursor = function drawFakeTextCursor(state) {
|
|
92
|
+
if (!(state.selection instanceof FakeTextCursorSelection)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
var node = document.createElement('div');
|
|
96
|
+
node.className = 'ProseMirror-fake-text-cursor';
|
|
97
|
+
return DecorationSet.create(state.doc, [Decoration.widget(state.selection.head, node, {
|
|
98
|
+
key: 'Cursor'
|
|
99
|
+
})]);
|
|
100
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as placeholderTextPlugin } from './plugin';
|