Skip to content

Commit ea48290

Browse files
authored
Merge pull request #27 from Patternslib/shared-toolbar
fix: Allow to share one toolbar with multiple tiptap instances.
2 parents b4c6e98 + 86022a7 commit ea48290

File tree

8 files changed

+243
-6
lines changed

8 files changed

+243
-6
lines changed

src/extensions/embed.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { focus_handler } from "../focus-handler";
33
import log from "../tiptap";
44
import { Node, mergeAttributes } from "@tiptap/core";
55
import { Plugin } from "prosemirror-state";
6+
import dom from "@patternslib/patternslib/src/core/dom";
67
import events from "@patternslib/patternslib/src/core/events";
78

89
let panel_observer;
@@ -104,7 +105,15 @@ function embed_panel({ app }) {
104105

105106
export function init({ app, button }) {
106107
// Initialize modal after it has injected.
107-
button.addEventListener("pat-modal-ready", () => embed_panel({ app: app }));
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 });
116+
});
108117
}
109118

110119
export const factory = () => {

src/extensions/image-figure.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { focus_handler } from "../focus-handler";
22
import log from "../tiptap";
33
import { Node, mergeAttributes } from "@tiptap/core";
44
import { Plugin } from "prosemirror-state";
5+
import dom from "@patternslib/patternslib/src/core/dom";
56
import events from "@patternslib/patternslib/src/core/events";
67

78
let panel_observer;
@@ -120,7 +121,15 @@ function image_panel({ app }) {
120121

121122
export function init({ app, button }) {
122123
// Initialize modal after it has injected.
123-
button.addEventListener("pat-modal-ready", () => image_panel({ app: app }));
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 });
132+
});
124133
}
125134

126135
export const factory = () => {

src/extensions/link.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { context_menu, context_menu_close } from "../context_menu";
22
import { focus_handler } from "../focus-handler";
33
import log from "../tiptap";
44
import LinkExtension from "@tiptap/extension-link";
5+
import dom from "@patternslib/patternslib/src/core/dom";
56
import events from "@patternslib/patternslib/src/core/events";
67
import utils from "@patternslib/patternslib/src/core/utils";
78

@@ -189,7 +190,15 @@ async function link_panel({ app }) {
189190

190191
export function init({ app, button }) {
191192
// Initialize modal after it has injected.
192-
button.addEventListener("pat-modal-ready", () => link_panel({ app: app }));
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 });
201+
});
193202

194203
app.editor.on("selectionUpdate", async () => {
195204
app.editor.isActive("link")

src/extensions/source.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { focus_handler } from "../focus-handler";
22
import log from "../tiptap";
3+
import dom from "@patternslib/patternslib/src/core/dom";
34
import events from "@patternslib/patternslib/src/core/events";
45

56
let panel_observer;
@@ -63,5 +64,13 @@ function source_panel({ app }) {
6364

6465
export function init({ app, button }) {
6566
// Initialize modal after it has injected.
66-
button.addEventListener("pat-modal-ready", () => source_panel({ app: app }));
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 });
75+
});
6776
}

src/index-many-single-toolbar.html

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5+
<title>tiptap demo</title>
6+
<script src="/bundle.min.js" type="text/javascript" charset="utf-8"></script>
7+
<style type="text/css">
8+
.tiptap-editor__content {
9+
border: 1px solid black;
10+
padding: 1em;
11+
}
12+
.ProseMirror:focus {
13+
outline: none;
14+
}
15+
.active {
16+
background-color: green;
17+
}
18+
table, td, th {
19+
border: 1px solid black;
20+
}
21+
th {
22+
background-color: grey;
23+
font-weight: bold;
24+
}
25+
26+
/* Placeholder (at the top) */
27+
.ProseMirror p.is-editor-empty:first-child::before {
28+
content: attr(data-placeholder);
29+
float: left;
30+
color: #ced4da;
31+
pointer-events: none;
32+
height: 0;
33+
}
34+
35+
.tooltip-container a,
36+
.tooltip-container a:visited {
37+
color: white;
38+
}
39+
</style>
40+
</head>
41+
<body>
42+
43+
<h1>TipTap examples</h1>
44+
45+
<div id="tiptap-toolbar">
46+
<button type="button" class="button-heading-level-1">Header level 1</button>
47+
<button type="button" class="button-heading-level-2">Header level 2</button>
48+
<button type="button" class="button-heading-level-3">Header level 3</button>
49+
<button type="button" class="button-paragraph">Normal</button>
50+
<button type="button" class="button-bold">Bold</button>
51+
<button type="button" class="button-italic">Italic</button>
52+
<button type="button" class="button-strike">Strike</button>
53+
<button type="button" class="button-unordered-list">Bullet list</button>
54+
<button type="button" class="button-ordered-list">Ordered list</button>
55+
<button type="button" class="button-undo">Undo</button>
56+
<button type="button" class="button-redo">Redo</button>
57+
<a class="button-link pat-modal" href="#modal-link">Link</a>
58+
</div>
59+
60+
61+
<section>
62+
<h2>TipTap -1</h2>
63+
<textarea
64+
class="pat-tiptap pat-autofocus"
65+
data-pat-tiptap="
66+
toolbar-external: #tiptap-toolbar;
67+
link-panel: #pat-modal .link-panel;
68+
link-menu: #context-menu-link"
69+
placeholder="Your poem goes here...">
70+
</textarea>
71+
</section>
72+
73+
<section>
74+
<h2>TipTap -2</h2>
75+
<textarea
76+
class="pat-tiptap pat-autofocus"
77+
data-pat-tiptap="
78+
toolbar-external: #tiptap-toolbar;
79+
link-panel: #pat-modal .link-panel;
80+
link-menu: #context-menu-link"
81+
placeholder="Your poem goes here...">
82+
</textarea>
83+
</section>
84+
85+
<section>
86+
<h2>TipTap -3</h2>
87+
<textarea
88+
class="pat-tiptap pat-autofocus"
89+
data-pat-tiptap="
90+
toolbar-external: #tiptap-toolbar;
91+
link-panel: #pat-modal .link-panel;
92+
link-menu: #context-menu-link"
93+
placeholder="Your poem goes here...">
94+
</textarea>
95+
</section>
96+
97+
<section>
98+
<h2>TipTap -4</h2>
99+
<textarea
100+
class="pat-tiptap pat-autofocus"
101+
data-pat-tiptap="
102+
toolbar-external: #tiptap-toolbar;
103+
link-panel: #pat-modal .link-panel;
104+
link-menu: #context-menu-link"
105+
placeholder="Your poem goes here...">
106+
</textarea>
107+
</section>
108+
109+
<section>
110+
<h2>TipTap -5</h2>
111+
<textarea
112+
class="pat-tiptap pat-autofocus"
113+
data-pat-tiptap="
114+
toolbar-external: #tiptap-toolbar;
115+
link-panel: #pat-modal .link-panel;
116+
link-menu: #context-menu-link"
117+
placeholder="Your poem goes here...">
118+
</textarea>
119+
</section>
120+
121+
<template id="modal-link">
122+
<h1>Add / Edit Link</h1>
123+
<form class="link-panel">
124+
<label>
125+
Link URL:
126+
<input type="text" name="tiptap-href"/>
127+
</label>
128+
<label>
129+
Link Text:
130+
<input type="text" name="tiptap-text"/>
131+
</label>
132+
<label>
133+
Open in new window:
134+
<input type="checkbox" name="tiptap-target" value="_blank" />
135+
</label>
136+
<button class="close-panel" type="button" name="tiptap-confirm">submit</button>
137+
<button class="close-panel" type="button" name="tiptap-remove">remove link</button>
138+
</form>
139+
</template>
140+
141+
<template id="context-menu-link">
142+
<ul class="tiptap-link-context-menu">
143+
<li>
144+
<a
145+
class="close-panel tiptap-open-new-link"
146+
target="_blank"
147+
href="">Visit linked web page</a>
148+
</li>
149+
<li>
150+
<button
151+
type="button"
152+
class="close-panel tiptap-edit-link">Edit link</button>
153+
</li>
154+
<li>
155+
<button
156+
type="button"
157+
class="close-panel tiptap-unlink">Unlink</button>
158+
</li>
159+
</ul>
160+
</template>
161+
162+
</body>
163+
</html>

src/tiptap.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Base from "@patternslib/patternslib/src/core/base";
22
import Parser from "@patternslib/patternslib/src/core/parser";
33
import Registry from "@patternslib/patternslib/src/core/registry";
4+
import dom from "@patternslib/patternslib/src/core/dom";
45
import events from "@patternslib/patternslib/src/core/events";
56
import logging from "@patternslib/patternslib/src/core/logging";
67
import utils from "@patternslib/patternslib/src/core/utils";
@@ -151,6 +152,10 @@ export default Base.extend({
151152
// Note: ``this`` is the pattern instance.
152153
utils.timeout(1); // short timeout to ensure focus class is set even if tiptap_blur_handler is called concurrently.
153154
this.toolbar_el?.classList.add("tiptap-focus");
155+
156+
// Set the current focused pat-tiptap instance on the toolbar element.
157+
this.toolbar_el &&
158+
dom.set_data(this.toolbar_el, "tiptap-instance", this);
154159
},
155160
onBlur: () => {
156161
// Note: ``this`` is the pattern instance.

src/tiptap.test.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ describe("pat-tiptap", () => {
9999
new Pattern(document.querySelectorAll(".pat-tiptap")[1]);
100100
await utils.timeout(1);
101101

102+
const containers = document.querySelectorAll(".tiptap-container");
103+
104+
containers[0].querySelector("[contenteditable]").focus(); // Set focus to bypass toolbar check
102105
document
103106
.querySelector("#tiptap-external-toolbar-1 .button-link")
104107
.dispatchEvent(new Event("pat-modal-ready"));
@@ -109,6 +112,7 @@ describe("pat-tiptap", () => {
109112
document.querySelector("#link-panel [name=tiptap-confirm]").dispatchEvent(new Event("click")); // prettier-ignore
110113
await utils.timeout(1);
111114

115+
containers[1].querySelector("[contenteditable]").focus(); // Set focus to bypass toolbar check
112116
document
113117
.querySelector("#tiptap-external-toolbar-2 .button-link")
114118
.dispatchEvent(new Event("pat-modal-ready"));
@@ -119,8 +123,6 @@ describe("pat-tiptap", () => {
119123
document.querySelector("#link-panel [name=tiptap-confirm]").dispatchEvent(new Event("click")); // prettier-ignore
120124
await utils.timeout(1);
121125

122-
const containers = document.querySelectorAll(".tiptap-container");
123-
124126
const anchor1 = containers[0].querySelector("a");
125127
expect(anchor1).toBeTruthy();
126128
expect(anchor1.href).toBe("https://url1.com/");
@@ -310,6 +312,8 @@ describe("pat-tiptap", () => {
310312
new Pattern(document.querySelector(".pat-tiptap"));
311313
await utils.timeout(1);
312314

315+
document.querySelector(".tiptap-container [contenteditable]").focus(); // Set focus to bypass toolbar check
316+
313317
document
314318
.querySelector("#tiptap-external-toolbar .button-link")
315319
.dispatchEvent(new Event("pat-modal-ready"));
@@ -350,6 +354,8 @@ describe("pat-tiptap", () => {
350354
new Pattern(document.querySelector(".pat-tiptap"));
351355
await utils.timeout(1);
352356

357+
document.querySelector(".tiptap-container [contenteditable]").focus(); // Set focus to bypass toolbar check
358+
353359
document
354360
.querySelector("#tiptap-external-toolbar .button-image")
355361
.dispatchEvent(new Event("pat-modal-ready"));
@@ -396,6 +402,8 @@ describe("pat-tiptap", () => {
396402
new Pattern(document.querySelector(".pat-tiptap"));
397403
await utils.timeout(1);
398404

405+
document.querySelector(".tiptap-container [contenteditable]").focus(); // Set focus to bypass toolbar check
406+
399407
document
400408
.querySelector("#tiptap-external-toolbar .button-image")
401409
.dispatchEvent(new Event("pat-modal-ready"));
@@ -435,6 +443,8 @@ describe("pat-tiptap", () => {
435443
new Pattern(document.querySelector(".pat-tiptap"));
436444
await utils.timeout(1);
437445

446+
document.querySelector(".tiptap-container [contenteditable]").focus(); // Set focus to bypass toolbar check
447+
438448
document
439449
.querySelector("#tiptap-external-toolbar .button-image")
440450
.dispatchEvent(new Event("pat-modal-ready"));
@@ -507,6 +517,8 @@ describe("pat-tiptap", () => {
507517
new Pattern(document.querySelector(".pat-tiptap"));
508518
await utils.timeout(1);
509519

520+
document.querySelector(".tiptap-container [contenteditable]").focus(); // Set focus to bypass toolbar check
521+
510522
document
511523
.querySelector("#tiptap-external-toolbar .button-embed")
512524
.dispatchEvent(new Event("pat-modal-ready"));
@@ -549,6 +561,8 @@ describe("pat-tiptap", () => {
549561
new Pattern(document.querySelector(".pat-tiptap"));
550562
await utils.timeout(1);
551563

564+
document.querySelector(".tiptap-container [contenteditable]").focus(); // Set focus to bypass toolbar check
565+
552566
document
553567
.querySelector("#tiptap-external-toolbar .button-embed")
554568
.dispatchEvent(new Event("pat-modal-ready"));
@@ -588,6 +602,8 @@ describe("pat-tiptap", () => {
588602
new Pattern(document.querySelector(".pat-tiptap"));
589603
await utils.timeout(1);
590604

605+
document.querySelector(".tiptap-container [contenteditable]").focus(); // Set focus to bypass toolbar check
606+
591607
document
592608
.querySelector("#tiptap-external-toolbar .button-embed")
593609
.dispatchEvent(new Event("pat-modal-ready"));
@@ -630,6 +646,8 @@ describe("pat-tiptap", () => {
630646
new Pattern(document.querySelector(".pat-tiptap"));
631647
await utils.timeout(1);
632648

649+
document.querySelector(".tiptap-container [contenteditable]").focus(); // Set focus to bypass toolbar check
650+
633651
document
634652
.querySelector("#tiptap-external-toolbar .button-embed")
635653
.dispatchEvent(new Event("pat-modal-ready"));

0 commit comments

Comments
 (0)