Skip to content

Commit 3496654

Browse files
authored
Merge pull request #29 from Patternslib/link-panel-fixes
feat: Always close context menu (link, mentions, tagging)‌ when click…
2 parents 6906536 + 2b00839 commit 3496654

File tree

8 files changed

+169
-117
lines changed

8 files changed

+169
-117
lines changed

src/context_menu.js

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Registry from "@patternslib/patternslib/src/core/registry";
2+
import dom from "@patternslib/patternslib/src/core/dom";
23
import events from "@patternslib/patternslib/src/core/events";
34
import patTooltip from "@patternslib/patternslib/src/pat/tooltip/tooltip";
45
import { posToDOMRect } from "@tiptap/core";
@@ -15,15 +16,19 @@ export async function context_menu({
1516
const prev_node = PREV_NODE;
1617
const cur_node = (PREV_NODE = editor.state.doc.nodeAt(editor.state.selection.from));
1718

18-
if (instance !== null && cur_node !== prev_node) {
19+
if (
20+
instance !== null &&
21+
(cur_node !== prev_node || !instance?.tippy.popperInstance)
22+
) {
1923
// Close context menu, when new node is selected.
20-
instance = context_menu_close({
24+
context_menu_close({
2125
instance: instance,
2226
pattern_name: pattern.name,
2327
});
28+
instance = null;
2429
}
2530

26-
if (!instance) {
31+
if (!instance || !instance?.tippy.popperInstance) {
2732
// Only re-initialize when not already opened.
2833

2934
// 1) Dynamically register a pattern to be used in the context menu
@@ -57,6 +62,35 @@ export async function context_menu({
5762
getReferenceClientRect: () => reference_position,
5863
});
5964

65+
events.add_event_listener(
66+
document,
67+
"mousedown",
68+
"tiptap--context_menu_close--click",
69+
(e) => {
70+
if (
71+
[e.target, ...dom.get_parents(e.target)].includes(
72+
instance?.tippy.popper
73+
)
74+
) {
75+
// Do not close the context menu if we click in it.
76+
return;
77+
}
78+
context_menu_close({ instance: instance, pattern_name: pattern.name });
79+
}
80+
);
81+
events.add_event_listener(
82+
document,
83+
"keydown",
84+
"tiptap--context_menu_close--esc",
85+
(e) => {
86+
if (e.key !== "Escape") {
87+
// Not a closing key.
88+
return;
89+
}
90+
context_menu_close({ instance: instance, pattern_name: pattern.name });
91+
}
92+
);
93+
6094
instance.show();
6195
} else {
6296
instance.get_content(url);
@@ -77,5 +111,7 @@ export function context_menu_close({ instance, pattern_name }) {
77111
if (pattern_name) {
78112
delete Registry.patterns[pattern_name];
79113
}
114+
events.remove_event_listener(document, "tiptap--context_menu_close--click");
115+
events.remove_event_listener(document, "tiptap--context_menu_close--esc");
80116
return null;
81117
}

src/extensions/embed.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// From: tiptap/demos/src/Experiments/GenericFigure/Vue/figure.ts
22
import { focus_handler } from "../focus-handler";
3-
import log from "../tiptap";
3+
import { log } from "../tiptap";
44
import { Node, mergeAttributes } from "@tiptap/core";
55
import { Plugin } from "prosemirror-state";
66
import dom from "@patternslib/patternslib/src/core/dom";
@@ -105,14 +105,20 @@ function embed_panel({ app }) {
105105

106106
export function init({ app, button }) {
107107
// Initialize modal after it has injected.
108-
button.addEventListener("pat-modal-ready", () => {
109-
if (dom.get_data(app.toolbar_el, "tiptap-instance", null) !== app) {
110-
// If this pat-tiptap instance is not the one which was last
111-
// focused, just return and do nothing.
112-
// This might be due to one toolbar shared by multiple editors.
113-
return;
114-
}
115-
embed_panel({ app: app });
108+
button.addEventListener("click", () => {
109+
document.addEventListener(
110+
"pat-modal-ready",
111+
() => {
112+
if (dom.get_data(app.toolbar_el, "tiptap-instance", null) !== app) {
113+
// If this pat-tiptap instance is not the one which was last
114+
// focused, just return and do nothing.
115+
// This might be due to one toolbar shared by multiple editors.
116+
return;
117+
}
118+
embed_panel({ app: app });
119+
},
120+
{ once: true }
121+
);
116122
});
117123
}
118124

src/extensions/image-figure.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { focus_handler } from "../focus-handler";
2-
import log from "../tiptap";
2+
import { log } from "../tiptap";
33
import { Node, mergeAttributes } from "@tiptap/core";
44
import { Plugin } from "prosemirror-state";
55
import dom from "@patternslib/patternslib/src/core/dom";
@@ -121,14 +121,20 @@ function image_panel({ app }) {
121121

122122
export function init({ app, button }) {
123123
// Initialize modal after it has injected.
124-
button.addEventListener("pat-modal-ready", () => {
125-
if (dom.get_data(app.toolbar_el, "tiptap-instance", null) !== app) {
126-
// If this pat-tiptap instance is not the one which was last
127-
// focused, just return and do nothing.
128-
// This might be due to one toolbar shared by multiple editors.
129-
return;
130-
}
131-
image_panel({ app: app });
124+
button.addEventListener("click", () => {
125+
document.addEventListener(
126+
"pat-modal-ready",
127+
() => {
128+
if (dom.get_data(app.toolbar_el, "tiptap-instance", null) !== app) {
129+
// If this pat-tiptap instance is not the one which was last
130+
// focused, just return and do nothing.
131+
// This might be due to one toolbar shared by multiple editors.
132+
return;
133+
}
134+
image_panel({ app: app });
135+
},
136+
{ once: true }
137+
);
132138
});
133139
}
134140

src/extensions/link.js

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { context_menu, context_menu_close } from "../context_menu";
22
import { focus_handler } from "../focus-handler";
3-
import log from "../tiptap";
3+
import { log } from "../tiptap";
44
import LinkExtension from "@tiptap/extension-link";
55
import dom from "@patternslib/patternslib/src/core/dom";
66
import events from "@patternslib/patternslib/src/core/events";
@@ -15,8 +15,15 @@ function pattern_link_context_menu({ app }) {
1515
return {
1616
name: "tiptap-link-context-menu",
1717
trigger: ".tiptap-link-context-menu",
18-
async init($el) {
18+
init($el) {
1919
const el = $el[0];
20+
21+
if (dom.get_data(el, this.name)) {
22+
// Prevent double initialization.
23+
return;
24+
}
25+
dom.set_data(el, this.name, this);
26+
2027
focus_handler(el);
2128

2229
const btn_open = el.querySelector(".tiptap-open-new-link");
@@ -28,30 +35,32 @@ function pattern_link_context_menu({ app }) {
2835
if (attrs?.href) {
2936
btn_open.setAttribute("href", attrs.href);
3037
}
31-
btn_open.addEventListener(
32-
"click",
33-
() =>
34-
(context_menu_instance = context_menu_close({
35-
instance: context_menu_instance,
36-
pattern_name: this.name,
37-
}))
38-
);
38+
btn_open.addEventListener("click", () => {
39+
context_menu_close({
40+
instance: context_menu_instance,
41+
pattern_name: this.name,
42+
});
43+
context_menu_instance = null;
44+
});
3945
}
4046

4147
btn_edit &&
4248
btn_edit.addEventListener("click", () => {
43-
context_menu_instance = context_menu_close({
49+
context_menu_close({
4450
instance: context_menu_instance,
4551
pattern_name: this.name,
4652
});
53+
context_menu_instance = null;
4754
app.toolbar.link.click();
4855
});
56+
4957
btn_unlink &&
5058
btn_unlink.addEventListener("click", () => {
51-
context_menu_instance = context_menu_close({
59+
context_menu_close({
5260
instance: context_menu_instance,
5361
pattern_name: this.name,
5462
});
63+
context_menu_instance = null;
5564
app.editor.chain().focus().unsetLink().run();
5665
});
5766
},
@@ -60,10 +69,11 @@ function pattern_link_context_menu({ app }) {
6069

6170
async function link_panel({ app }) {
6271
// Close eventual opened link context menus.
63-
context_menu_instance = context_menu_close({
72+
context_menu_close({
6473
instance: context_menu_instance,
6574
pattern_name: "tiptap-link-context-menu",
6675
});
76+
context_menu_instance = null;
6777

6878
const link_panel = document.querySelector(app.options.link?.panel);
6979
if (!link_panel) {
@@ -72,6 +82,11 @@ async function link_panel({ app }) {
7282
}
7383
focus_handler(link_panel);
7484

85+
// Store the current cursor position.
86+
// While extending the selection below the cursor position is changed and
87+
// we want it back where we left.
88+
const last_cursor_position = app.editor.state.selection.$head.pos;
89+
7590
const reinit = () => {
7691
const link_href = link_panel.querySelector("[name=tiptap-href]");
7792
const link_text = link_panel.querySelector("[name=tiptap-text]");
@@ -88,8 +103,7 @@ async function link_panel({ app }) {
88103

89104
if (is_link) {
90105
// Extend the selection to whole link.
91-
// Necessary for link updates below in the update_callback
92-
// to get the selection right which is replaced.
106+
// Necessary to get the whole link scope and the correct text.
93107
dont_open_context_menu = true; // setting a selection on a link would open the context menu.
94108
app.editor.commands.extendMarkRange("link");
95109
dont_open_context_menu = false;
@@ -121,9 +135,6 @@ async function link_panel({ app }) {
121135

122136
const update_callback = (set_focus) => {
123137
const cmd = app.editor.chain();
124-
if (set_focus === true) {
125-
cmd.focus();
126-
}
127138
const link_text_value = (link_text ? link_text.value : text_content) || "";
128139
cmd.command(async ({ tr }) => {
129140
// create = update
@@ -137,6 +148,10 @@ async function link_panel({ app }) {
137148
.text(link_text_value)
138149
.mark([mark]);
139150
tr.replaceSelectionWith(link_node, false);
151+
if (set_focus === true) {
152+
// Set the cursor back to the position where we left.
153+
cmd.focus().setTextSelection(last_cursor_position);
154+
}
140155
return true;
141156
});
142157
cmd.run();
@@ -190,14 +205,20 @@ async function link_panel({ app }) {
190205

191206
export function init({ app, button }) {
192207
// Initialize modal after it has injected.
193-
button.addEventListener("pat-modal-ready", () => {
194-
if (dom.get_data(app.toolbar_el, "tiptap-instance", null) !== app) {
195-
// If this pat-tiptap instance is not the one which was last
196-
// focused, just return and do nothing.
197-
// This might be due to one toolbar shared by multiple editors.
198-
return;
199-
}
200-
link_panel({ app: app });
208+
button.addEventListener("click", () => {
209+
document.addEventListener(
210+
"pat-modal-ready",
211+
() => {
212+
if (dom.get_data(app.toolbar_el, "tiptap-instance", null) !== app) {
213+
// If this pat-tiptap instance is not the one which was last
214+
// focused, just return and do nothing.
215+
// This might be due to one toolbar shared by multiple editors.
216+
return;
217+
}
218+
link_panel({ app: app });
219+
},
220+
{ once: true }
221+
);
201222
});
202223

203224
app.editor.on("selectionUpdate", async () => {
@@ -208,8 +229,8 @@ export function init({ app, button }) {
208229
? button.classList.remove("disabled")
209230
: button.classList.add("disabled");
210231

211-
// Temporarily don't open the context menu.
212-
if (dont_open_context_menu && !app.options.link?.menu) {
232+
if (dont_open_context_menu) {
233+
// Temporarily don't open the context menu.
213234
return;
214235
}
215236

@@ -219,10 +240,11 @@ export function init({ app, button }) {
219240
if (!app.editor.isActive("link")) {
220241
if (context_menu_instance) {
221242
// If open, close.
222-
context_menu_instance = context_menu_close({
243+
context_menu_close({
223244
instance: context_menu_instance,
224245
pattern_name: "tiptap-link-context-menu",
225246
});
247+
context_menu_instance = null;
226248
}
227249
return;
228250
}

src/extensions/source.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { focus_handler } from "../focus-handler";
2-
import log from "../tiptap";
2+
import { log } from "../tiptap";
33
import dom from "@patternslib/patternslib/src/core/dom";
44
import events from "@patternslib/patternslib/src/core/events";
55

@@ -64,13 +64,19 @@ function source_panel({ app }) {
6464

6565
export function init({ app, button }) {
6666
// Initialize modal after it has injected.
67-
button.addEventListener("pat-modal-ready", () => {
68-
if (dom.get_data(app.toolbar_el, "tiptap-instance", null) !== app) {
69-
// If this pat-tiptap instance is not the one which was last
70-
// focused, just return and do nothing.
71-
// This might be due to one toolbar shared by multiple editors.
72-
return;
73-
}
74-
source_panel({ app: app });
67+
button.addEventListener("click", () => {
68+
document.addEventListener(
69+
"pat-modal-ready",
70+
() => {
71+
if (dom.get_data(app.toolbar_el, "tiptap-instance", null) !== app) {
72+
// If this pat-tiptap instance is not the one which was last
73+
// focused, just return and do nothing.
74+
// This might be due to one toolbar shared by multiple editors.
75+
return;
76+
}
77+
source_panel({ app: app });
78+
},
79+
{ once: true }
80+
);
7581
});
7682
}

0 commit comments

Comments
 (0)