Skip to content

Commit b50b2f0

Browse files
committed
fix(pat-tinymce): construct the modal implementation for the link modal
The pat-plone-modal lazification left links.js creating its modal via registry.patterns["plone-modal"].init(...). That static initializer constructs the *registered* pattern — now the thin registration module, whose methods (show/hide/...) only appear after an async graft — while LinkModal calls this.modal.show() synchronously right after construction. First click on Insert Link/Image threw and the modal stayed broken, which also failed every robot test funneling through it (tinymce, contentbrowser, linkintegrity). Construct modal--implementation directly instead; mockupParser reproduces the option parsing of the registry path. links.js is only reached via the lazily-loaded tinymce implementation, so the modal body stays out of the eager bundle. Add a regression test asserting the implementation exposes its methods synchronously.
1 parent cc55e53 commit b50b2f0

2 files changed

Lines changed: 51 additions & 16 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import $ from "jquery";
2+
import Modal from "./modal--implementation";
3+
4+
// The tinymce link modal (pat-tinymce js/links.js) constructs the modal
5+
// imperatively (`new Modal(...)`) and calls show() synchronously right after.
6+
// The implementation must therefore be a constructable pattern that exposes
7+
// its methods synchronously — not a config object grafted asynchronously by
8+
// the thin modal.js registration module.
9+
describe("modal implementation", function () {
10+
let $el;
11+
beforeEach(function () {
12+
$el = $("<div/>").appendTo("body");
13+
});
14+
afterEach(function () {
15+
$el.remove();
16+
});
17+
18+
it("is constructable and exposes its methods synchronously", function () {
19+
const modal = new Modal($el, { html: "<div>content</div>" });
20+
expect(typeof modal.show).toBe("function");
21+
expect(typeof modal.hide).toBe("function");
22+
});
23+
});

src/pat/tinymce/js/links.js

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import Base from "@patternslib/patternslib/src/core/base";
22
import events from "@patternslib/patternslib/src/core/events";
3-
import registry from "@patternslib/patternslib/src/core/registry";
3+
import mockupParser from "@patternslib/patternslib/src/core/mockup-parser";
44
import $ from "jquery";
55
import _ from "underscore";
66

77
import tinymce from "tinymce/tinymce";
88
import "../../autotoc/autotoc";
9-
import "../../modal/modal";
9+
// The full modal implementation, not the thin registration module: the modal
10+
// instance is used synchronously (show() right after construction), so its
11+
// methods must live on the prototype. This module is only reached via the
12+
// lazily-loaded tinymce implementation, so it stays out of the eager bundle.
13+
import Modal from "../../modal/modal--implementation";
1014
import ImageTemplate from "../templates/image.xml";
1115
import LinkTemplate from "../templates/link.xml";
1216

@@ -408,21 +412,29 @@ export default Base.extend({
408412
this.dom = this.tiny.dom;
409413
this.linkType = this.options.initialLinkType;
410414
this.linkTypes = {};
411-
this.modal = registry.patterns["plone-modal"].init(this.$el, {
412-
html: this.generateModalHtml(),
413-
content: null,
414-
buttons: ".plone-btn",
415-
reloadWindowOnClose: false,
416-
templateOptions: {
417-
classDialog: "modal-dialog modal-lg",
415+
// Construct the implementation class directly — its static init()
416+
// (like registry.patterns["plone-modal"].init) would instantiate the
417+
// registered thin pattern, whose methods only appear after an async
418+
// graft, while show() is called synchronously right after this.
419+
// mockupParser reproduces the option parsing of the registry path.
420+
this.modal = new Modal(
421+
this.$el,
422+
mockupParser.getOptions(this.$el, "plone-modal", {
423+
html: this.generateModalHtml(),
424+
content: null,
425+
buttons: ".plone-btn",
418426
reloadWindowOnClose: false,
419-
},
420-
actionOptions: { reloadWindowOnClose: false },
421-
backdropOptions: {
422-
zIndex: "1340",
423-
closeOnClick: false,
424-
},
425-
});
427+
templateOptions: {
428+
classDialog: "modal-dialog modal-lg",
429+
reloadWindowOnClose: false,
430+
},
431+
actionOptions: { reloadWindowOnClose: false },
432+
backdropOptions: {
433+
zIndex: "1340",
434+
closeOnClick: false,
435+
},
436+
})
437+
);
426438
this.modal.on("shown", (e) => {
427439
this.modalShown.apply(this, [e]);
428440
});

0 commit comments

Comments
 (0)