mui-tiptap
Version:
A Material-UI (MUI) styled WYSIWYG rich text editor, using Tiptap
83 lines (82 loc) • 3.96 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.scrollToCurrentHeadingAnchor = void 0;
const extension_heading_1 = require("@tiptap/extension-heading");
const react_1 = require("@tiptap/react");
const HeadingWithAnchorComponent_1 = __importDefault(require("./HeadingWithAnchorComponent"));
/**
* A modified version of Tiptap’s `Heading` extension
* (https://tiptap.dev/api/nodes/heading), with dynamic GitHub-like anchor links
* for every heading you add. An anchor link button appears to the left of each
* heading when you hovering over it, when the `editor` has `editable` set to
* `false`. This allows users to share links and jump to specific headings
* within your rendered editor content. It can also accommodate building a table
* of contents or outline more easily.
*/
const HeadingWithAnchor = extension_heading_1.Heading.extend({
addOptions() {
var _a;
return Object.assign(Object.assign({}, (_a = this.parent) === null || _a === void 0 ? void 0 : _a.call(this)), { scrollToAnchorOnMount: true });
},
onCreate() {
// It seems that when `onCreate` is called via this extension `onCreate`
// definition, our content and HeadingWithAnchor React node-views have yet
// to be rendered. (Also notably, react renderers are async so don't appear
// even when the rest of the document HTML first shows up, as mentioned in
// https://github.com/ueberdosis/tiptap/issues/1527#issuecomment-888380206.)
// Delaying until the end of the event loop with setTimeout should do the
// trick.
if (this.options.scrollToAnchorOnMount) {
setTimeout(() => {
scrollToCurrentHeadingAnchor(this.editor);
});
}
},
// Although we could render this using just HTML presumably (via `renderHTML`)
// and don't need any fancy interaction with React, doing so allows us to use
// a MUI SVG icon as well as MUI styling
addNodeView() {
// @ts-expect-error Our HeadingWithAnchorComponent component overrides the
// NodeViewProps to specify that the `node`'s `attrs` must contain the
// `level` key, as the base `Heading` extension requires, but
// `ReactNodeViewRenderer`'s type doesn't account for this.
return (0, react_1.ReactNodeViewRenderer)(HeadingWithAnchorComponent_1.default);
},
});
exports.default = HeadingWithAnchor;
/**
* If there's a URL hash string indicating we should be anchored to a specific
* heading, this function scrolls to that heading.
*
* We have to do this manually/programmatically after first render using this
* function, since when the page first loads, the editor content will not be
* mounted/rendered, so the browser doesn't move to the anchor automatically
* just from having the anchor in the URL. Note that we only want to do this
* once on mount/create.
*/
function scrollToCurrentHeadingAnchor(editor) {
if (editor.isDestroyed || !("heading" in editor.storage)) {
// If the editor is already removed/destroyed, or the heading extension
// isn't enabled, we can stop
return;
}
const currentHash = window.location.hash;
const elementId = currentHash.slice(1);
if (!elementId) {
return;
}
const elementForHash = window.document.getElementById(elementId);
// We'll only scroll if the given hash points to an element that's part of our
// editor content (i.e., ignore external anchors)
if (elementForHash && editor.options.element.contains(elementForHash)) {
elementForHash.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest",
});
}
}
exports.scrollToCurrentHeadingAnchor = scrollToCurrentHeadingAnchor;
;