Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Late initialization. #32

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 91 additions & 39 deletions src/tiptap.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,73 @@ parser.addAlias("context-menu-link", "link-menu");
parser.addAlias("context-menu-mentions", "mentions-menu");
parser.addAlias("context-menu-tags", "tags-menu");

parser.addArgument("lazy", false);

export default Base.extend({
name: "tiptap",
trigger: ".pat-tiptap",

async init() {
// Constructor
this.toolbar_el = null;
// Initialize the pattern and prepare initialization of the tiptap editor.
this.options = parser.parse(this.el, this.options);
this.is_form_el = ["TEXTAREA", "INPUT"].includes(this.el.tagName);

// Hide original element which will be replaced with tiptap instance.
this.el.style.display = "none";

// Create container for tiptap.
// In case of lazy initialization the container is an intermediate container
// which will later be replaced by a tiptap container, after tiptap
// has been initialized off-canvas and be ready.
this.tiptap_container = this.create_tiptap_container({
editable: this.options.lazy,
});
if (this.options.lazy) {
// Display the text content before tiptap is being loaded.
this.tiptap_container.innerHTML = this.get_textarea_text() || "<br>";
}
this.el.after(this.tiptap_container);

if (this.options.lazy) {
events.add_event_listener(
this.tiptap_container,
"focus",
"tiptap--initialization",
() => this.init_editor(),
{ once: true }
);
} else {
await this.init_editor();
}
},

get_textarea_text() {
// Textarea value getter
return this.is_form_el ? this.el.value : this.el.innerHTML;
},

set_textarea_text(value) {
// Textarea value setter
if (this.is_form_el) {
this.el.value = value;
} else {
this.el.innerHTML = value;
}
this.el.dispatchEvent(events.input_event());
},

create_tiptap_container({ editable = false }) {
const tiptap_container = document.createElement("div");
tiptap_container.setAttribute("class", "tiptap-container");
if (editable) {
tiptap_container.setAttribute("contenteditable", true);
tiptap_container.setAttribute("tabindex", "-1"); // make selectable.
}
return tiptap_container;
},

async init_editor() {
// Initialize the tiptap editor itself.

const TipTap = (await import("@tiptap/core")).Editor;
const ExtDocument = (await import("@tiptap/extension-document")).default;
Expand All @@ -44,37 +104,20 @@ export default Base.extend({

this.focus_handler = (await import("./focus-handler")).focus_handler;

this.options = parser.parse(this.el, this.options);

// Hide element which will be replaced with tiptap instance
this.el.style.display = "none";
// Create container for tiptap
const container = document.createElement("div");
container.setAttribute("class", "tiptap-container");
this.el.after(container);
this.toolbar_el = this.options.toolbarExternal
? document.querySelector(this.options.toolbarExternal)
: null;
if (this.toolbar_el) {
const focus_handler_targets = (await import("./focus-handler")).TARGETS; // prettier-ignore
focus_handler_targets.push(this.toolbar_el); // We register the focus handler on itself.
this.focus_handler(this.toolbar_el);
}

// Support for pat-autofocus and autofocus: Set focus depending on textarea's focus setting.
const set_focus =
this.el.classList.contains("pat-autofocus") ||
this.el.hasAttribute("autofocus");

const is_form_el = ["TEXTAREA", "INPUT"].includes(this.el.tagName);

const getText = () => {
// Textarea value getter
return is_form_el ? this.el.value : this.el.innerHTML;
};

const setText = (text) => {
// Textarea value setter
if (is_form_el) {
this.el.value = text;
} else {
this.el.innerHTML = text;
}
this.el.dispatchEvent(events.input_event());
};

const extra_extensions = [
// Allow non-paragraph line-breaks by default.
(await import("@tiptap/extension-hard-break")).default.configure(),
Expand Down Expand Up @@ -124,30 +167,39 @@ export default Base.extend({
);
}

this.toolbar_el = this.options.toolbarExternal
? document.querySelector(this.options.toolbarExternal)
: null;
if (this.toolbar_el) {
const focus_handler_targets = (await import("./focus-handler")).TARGETS; // prettier-ignore
focus_handler_targets.push(this.toolbar_el); // We register the focus handler on itself.
this.focus_handler(this.toolbar_el);
}

const toolbar_ext = await import("./toolbar");
this.toolbar = toolbar_ext.init_pre({ app: this });

// Late initialization - create new element where tiptap is initialized on.
// This will replace the intermediate element where we only showed the
// content until tiptap was initialized.
const tiptap_container = this.options.lazy
? this.create_tiptap_container({ editable: false })
: this.tiptap_container;

const self = this;
this.editor = new TipTap({
element: container,
element: tiptap_container,
extensions: [
ExtDocument,
ExtText,
ExtParagraph,
...(await toolbar_ext.init_extensions({ app: this })),
...extra_extensions,
],
content: getText(),
content: this.get_textarea_text(),
onCreate: () => {
if (this.options.lazy) {
// Late initialization - replace the intermediate tiptap container
// with the tiptap-initialized one.
this.tiptap_container.replaceWith(tiptap_container);
// We also need to set the this.tiptap_container to the new one.
this.tiptap_container = tiptap_container;
}
},
onUpdate() {
// Note: ``this`` is the editor instance.
setText(this.getHTML());
self.set_textarea_text(this.getHTML());
Registry.scan(this.view.dom);
},
onFocus: async () => {
Expand Down
20 changes: 10 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1701,10 +1701,10 @@
whybundled "^2.0.0"
yarn "^1.22.19"

"@patternslib/patternslib@*":
version "8.1.0"
resolved "https://registry.yarnpkg.com/@patternslib/patternslib/-/patternslib-8.1.0.tgz#165b07e8d53298ba98f248223d360d216b27b885"
integrity sha512-5j5l/JG2N6oGXhBq34jLRTpW+DjW0CijsBwDatIKloxI9o+V0XawQ2Rc9h3qFvXcryXXDxgcqVn0YXdS3l28uQ==
"@patternslib/patternslib@9.0.0":
version "9.0.0"
resolved "https://registry.yarnpkg.com/@patternslib/patternslib/-/patternslib-9.0.0.tgz#0684c92627bb1578b7178bd9f92f1d226e59bffd"
integrity sha512-0nrvhl+8EN5hcjxQaNohBowzDVol5m1ZG+4amYMXPBjE07/6l+gtQCcYn9arPgsxVJqdYxN9wg5LTirMhR/n3w==
dependencies:
"@fullcalendar/adaptive" "^5.11.0"
"@fullcalendar/core" "^5.11.0"
Expand All @@ -1717,17 +1717,17 @@
"@stomp/stompjs" "^6.1.2"
google-code-prettify "^1.0.5"
imagesloaded "^4.1.4"
intersection-observer "^0.12.0"
intersection-observer "^0.12.2"
jquery "^3.6.0"
jquery-jcrop "^0.9.13"
luxon "^2.3.2"
masonry-layout "^4.2.2"
moment "^2.29.3"
moment "^2.29.4"
moment-timezone "^0.5.34"
photoswipe "^4.1.3"
pikaday "^1.8.0"
promise-polyfill "^8.2.3"
screenfull "^6.0.1"
screenfull "^6.0.2"
select2 "^3.5.1"
showdown "^2.1.0"
showdown-prettify "^1.3.0"
Expand Down Expand Up @@ -5233,7 +5233,7 @@ interpret@^2.2.0:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==

intersection-observer@^0.12.0:
intersection-observer@^0.12.2:
version "0.12.2"
resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375"
integrity sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==
Expand Down Expand Up @@ -6504,7 +6504,7 @@ moment-timezone@^0.5.34:
dependencies:
moment ">= 2.9.0"

"moment@>= 2.9.0", moment@^2.29.3:
"moment@>= 2.9.0", moment@^2.29.4:
version "2.29.4"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
Expand Down Expand Up @@ -7820,7 +7820,7 @@ schema-utils@^4.0.0:
ajv-formats "^2.1.1"
ajv-keywords "^5.0.0"

screenfull@^6.0.1:
screenfull@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-6.0.2.tgz#3dbe4b8c4f8f49fb8e33caa8f69d0bca730ab238"
integrity sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw==
Expand Down