Skip to content

Commit e836d02

Browse files
committed
refactor(selection): new approach
1 parent 31ea580 commit e836d02

6 files changed

Lines changed: 91 additions & 50 deletions

File tree

packages/super-editor/src/assets/styles/elements/prosemirror.css

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,10 @@ https://github.com/ProseMirror/prosemirror-tables/blob/master/demo/index.html
353353
.ProseMirror-active-search-match {
354354
background-color: #ff6a0054;
355355
}
356-
357-
.ProseMirror span::selection {
356+
.ProseMirror span.sd-custom-selection::selection {
358357
background: transparent;
359358
}
359+
.sd-custom-selection {
360+
background-color: #d9d9d9;
361+
border-radius: 0.1em;
362+
}

packages/super-editor/src/assets/styles/extensions/comments.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
.sd-editor-comment-highlight:hover {
66
background-color: #1354ff55;
77
}
8+
9+
.sd-editor-comment-highlight.sd-custom-selection {
10+
background-color: #d6c0c6 !important;
11+
}

packages/super-editor/src/assets/styles/extensions/list-items.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@
2121

2222
/* temporary fix */
2323
.sd-editor-list-item-node-view .sd-custom-selection {
24-
font-size: inherit!important;
24+
font-size: inherit !important;
2525
}

packages/super-editor/src/assets/styles/layout/global.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,3 @@
1414
a {
1515
text-decoration: auto;
1616
}
17-
18-
.sd-custom-selection {
19-
background-color: #accef7;
20-
}

packages/super-editor/src/components/SuperEditor.vue

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup>
22
import 'tippy.js/dist/tippy.css';
33
import { NSkeleton } from 'naive-ui';
4-
import { ref, onMounted, onBeforeUnmount, shallowRef, reactive, markRaw, onDeactivated } from 'vue';
4+
import { ref, onMounted, onBeforeUnmount, shallowRef, reactive, markRaw } from 'vue';
55
import { Editor } from '@/index.js';
66
import { getStarterExtensions } from '@extensions/index.js';
77
import SlashMenu from './slash-menu/SlashMenu.vue';
@@ -223,26 +223,9 @@ const handleSuperEditorClick = (event) => {
223223
}
224224
};
225225
226-
const handleClickOutside = (event) => {
227-
const pmElement = editorElem.value?.querySelector('.ProseMirror');
228-
const isInsideEditor = pmElement?.contains(event.target);
229-
230-
if (!isInsideEditor) {
231-
editor.value?.setOptions({
232-
focusTarget: event.target,
233-
});
234-
}
235-
};
236-
237226
onMounted(() => {
238227
initializeData();
239228
if (props.options?.suppressSkeletonLoader || !props.options?.collaborationProvider) editorReady.value = true;
240-
241-
document.addEventListener('mousedown', handleClickOutside);
242-
});
243-
244-
onDeactivated(() => {
245-
document.removeEventListener('mousedown', handleClickOutside);
246229
});
247230
248231
const handleMarginClick = (event) => {

packages/super-editor/src/extensions/custom-selection/custom-selection.js

Lines changed: 80 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,108 @@
11
import { Extension } from '@core/Extension.js';
2-
import { AllSelection, Plugin, PluginKey, TextSelection } from 'prosemirror-state';
2+
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state';
33
import { Decoration, DecorationSet } from 'prosemirror-view';
44

55
export const CustomSelectionPluginKey = new PluginKey('CustomSelection');
66

7+
const handleClickOutside = (event, editor) => {
8+
const editorElem = document.querySelector('.editor-element');
9+
const isInsideEditor = editorElem?.contains(event.target);
10+
11+
if (!isInsideEditor) {
12+
editor.setOptions({
13+
focusTarget: event.target,
14+
});
15+
} else {
16+
editor.setOptions({
17+
focusTarget: null,
18+
});
19+
}
20+
};
21+
22+
function getFocusMeta(tr) {
23+
return tr.getMeta(CustomSelectionPluginKey);
24+
}
25+
26+
function setFocusMeta(tr, value) {
27+
return tr.setMeta(CustomSelectionPluginKey, value);
28+
}
29+
30+
function getFocusState(state) {
31+
return CustomSelectionPluginKey.getState(state);
32+
}
33+
734
export const CustomSelection = Extension.create({
835
name: 'customSelection',
9-
1036
addPmPlugins() {
37+
const editor = this.editor;
1138
const customSelectionPlugin = new Plugin({
1239
key: CustomSelectionPluginKey,
13-
1440
state: {
15-
init() {
16-
return DecorationSet.empty;
41+
init: () => false,
42+
apply: (tr, value) => {
43+
return getFocusMeta(tr) ?? value;
1744
},
18-
apply(tr, oldDecorationSet, oldState, newState) {
19-
const sel = tr.selection;
20-
let newDecos = [];
21-
22-
// Only apply to text selections or whole doc selections
23-
if (sel.from !== sel.to && (tr.doc.resolve(sel.from).parent.isTextblock || sel instanceof AllSelection)) {
24-
newDecos.push(
25-
Decoration.inline(sel.from, sel.to, {
26-
class: 'sd-custom-selection',
27-
}),
28-
);
29-
}
45+
},
46+
view: () => {
47+
document?.addEventListener('mousedown', (event) => handleClickOutside(event, editor));
3048

31-
return DecorationSet.create(newState.doc, newDecos);
32-
},
49+
return {
50+
destroy: () => {
51+
document?.removeEventListener('mouseout', handleClickOutside);
52+
},
53+
};
3354
},
3455
props: {
3556
handleDOMEvents: {
36-
focusout: (view, event) => {
37-
const isDropDownOption = this.editor.options.focusTarget?.getAttribute('data-dropdown-option');
38-
if (document.activeElement && !event.relatedTarget && !view.state.selection.empty && !isDropDownOption) {
57+
mousedown: (view) => {
58+
const { selection } = view.state;
59+
const isToolbarButton = this.editor.options.focusTarget?.closest('.toolbar-button');
60+
61+
if (!isToolbarButton) {
62+
view.dispatch(setFocusMeta(view.state.tr, false));
63+
}
64+
if (!selection.empty) {
3965
this.editor.setOptions({
4066
lastSelection: view.state.selection,
4167
});
4268
const clearSelectionTr = view.state.tr.setSelection(TextSelection.create(view.state.doc, 0));
4369

4470
view.dispatch(clearSelectionTr);
4571
}
46-
return false;
72+
},
73+
focus: (view) => {
74+
const isToolbarButton = this.editor.options.focusTarget?.closest('.toolbar-button');
75+
76+
if (isToolbarButton) {
77+
return;
78+
}
79+
80+
view.dispatch(setFocusMeta(view.state.tr, false));
81+
},
82+
83+
blur: (view) => {
84+
const isToolbarButton = this.editor.options.focusTarget?.closest('.toolbar-button');
85+
86+
if (isToolbarButton) {
87+
view.dispatch(setFocusMeta(view.state.tr, true));
88+
return;
89+
}
90+
91+
view.dispatch(setFocusMeta(view.state.tr, false));
4792
},
4893
},
49-
decorations(state) {
50-
return CustomSelectionPluginKey.getState(state);
94+
decorations: (state) => {
95+
const { selection, doc } = state;
96+
97+
if (selection.empty || !getFocusState(state)) {
98+
return null;
99+
}
100+
101+
return DecorationSet.create(doc, [
102+
Decoration.inline(selection.from, selection.to, {
103+
class: 'sd-custom-selection',
104+
}),
105+
]);
51106
},
52107
},
53108
});

0 commit comments

Comments
 (0)