fix: resolve TypeScript errors in frontend build
This commit is contained in:
21
node_modules/@tiptap/vue-3/LICENSE.md
generated
vendored
Normal file
21
node_modules/@tiptap/vue-3/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025, Tiptap GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
18
node_modules/@tiptap/vue-3/README.md
generated
vendored
Normal file
18
node_modules/@tiptap/vue-3/README.md
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# @tiptap/vue-3
|
||||
|
||||
[](https://www.npmjs.com/package/@tiptap/vue-3)
|
||||
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
||||
[](https://www.npmjs.com/package/@tiptap/vue-3)
|
||||
[](https://github.com/sponsors/ueberdosis)
|
||||
|
||||
## Introduction
|
||||
|
||||
Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.
|
||||
|
||||
## Official Documentation
|
||||
|
||||
Documentation can be found on the [Tiptap website](https://tiptap.dev).
|
||||
|
||||
## License
|
||||
|
||||
Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).
|
||||
655
node_modules/@tiptap/vue-3/dist/index.cjs
generated
vendored
Normal file
655
node_modules/@tiptap/vue-3/dist/index.cjs
generated
vendored
Normal file
@@ -0,0 +1,655 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/index.ts
|
||||
var index_exports = {};
|
||||
__export(index_exports, {
|
||||
Editor: () => Editor,
|
||||
EditorContent: () => EditorContent,
|
||||
MarkViewContent: () => MarkViewContent,
|
||||
NodeViewContent: () => NodeViewContent,
|
||||
NodeViewWrapper: () => NodeViewWrapper,
|
||||
VueMarkView: () => VueMarkView,
|
||||
VueMarkViewRenderer: () => VueMarkViewRenderer,
|
||||
VueNodeViewRenderer: () => VueNodeViewRenderer,
|
||||
VueRenderer: () => VueRenderer,
|
||||
markViewProps: () => markViewProps,
|
||||
nodeViewProps: () => nodeViewProps,
|
||||
useEditor: () => useEditor
|
||||
});
|
||||
module.exports = __toCommonJS(index_exports);
|
||||
|
||||
// src/Editor.ts
|
||||
var import_core = require("@tiptap/core");
|
||||
var import_vue = require("vue");
|
||||
function useDebouncedRef(value) {
|
||||
return (0, import_vue.customRef)((track, trigger) => {
|
||||
return {
|
||||
get() {
|
||||
track();
|
||||
return value;
|
||||
},
|
||||
set(newValue) {
|
||||
value = newValue;
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
trigger();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
var Editor = class extends import_core.Editor {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.contentComponent = null;
|
||||
this.appContext = null;
|
||||
this.reactiveState = useDebouncedRef(this.view.state);
|
||||
this.reactiveExtensionStorage = useDebouncedRef(this.extensionStorage);
|
||||
this.on("beforeTransaction", ({ nextState }) => {
|
||||
this.reactiveState.value = nextState;
|
||||
this.reactiveExtensionStorage.value = this.extensionStorage;
|
||||
});
|
||||
return (0, import_vue.markRaw)(this);
|
||||
}
|
||||
get state() {
|
||||
return this.reactiveState ? this.reactiveState.value : this.view.state;
|
||||
}
|
||||
get storage() {
|
||||
return this.reactiveExtensionStorage ? this.reactiveExtensionStorage.value : super.storage;
|
||||
}
|
||||
/**
|
||||
* Register a ProseMirror plugin.
|
||||
*/
|
||||
registerPlugin(plugin, handlePlugins) {
|
||||
const nextState = super.registerPlugin(plugin, handlePlugins);
|
||||
if (this.reactiveState) {
|
||||
this.reactiveState.value = nextState;
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
/**
|
||||
* Unregister a ProseMirror plugin.
|
||||
*/
|
||||
unregisterPlugin(nameOrPluginKey) {
|
||||
const nextState = super.unregisterPlugin(nameOrPluginKey);
|
||||
if (this.reactiveState && nextState) {
|
||||
this.reactiveState.value = nextState;
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
};
|
||||
|
||||
// src/EditorContent.ts
|
||||
var import_vue2 = require("vue");
|
||||
var EditorContent = (0, import_vue2.defineComponent)({
|
||||
name: "EditorContent",
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const rootEl = (0, import_vue2.ref)();
|
||||
const instance = (0, import_vue2.getCurrentInstance)();
|
||||
(0, import_vue2.watchEffect)(() => {
|
||||
const editor = props.editor;
|
||||
if (editor && editor.options.element && rootEl.value) {
|
||||
(0, import_vue2.nextTick)(() => {
|
||||
var _a;
|
||||
if (!rootEl.value || !((_a = editor.view.dom) == null ? void 0 : _a.parentNode)) {
|
||||
return;
|
||||
}
|
||||
const element = (0, import_vue2.unref)(rootEl.value);
|
||||
rootEl.value.append(...editor.view.dom.parentNode.childNodes);
|
||||
editor.contentComponent = instance.ctx._;
|
||||
if (instance) {
|
||||
editor.appContext = {
|
||||
...instance.appContext,
|
||||
// Vue internally uses prototype chain to forward/shadow injects across the entire component chain
|
||||
// so don't use object spread operator or 'Object.assign' and just set `provides` as is on editor's appContext
|
||||
// @ts-expect-error forward instance's 'provides' into appContext
|
||||
provides: instance.provides
|
||||
};
|
||||
}
|
||||
editor.setOptions({
|
||||
element
|
||||
});
|
||||
editor.createNodeViews();
|
||||
});
|
||||
}
|
||||
});
|
||||
(0, import_vue2.onBeforeUnmount)(() => {
|
||||
const editor = props.editor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
editor.contentComponent = null;
|
||||
editor.appContext = null;
|
||||
});
|
||||
return { rootEl };
|
||||
},
|
||||
render() {
|
||||
return (0, import_vue2.h)("div", {
|
||||
ref: (el) => {
|
||||
this.rootEl = el;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// src/NodeViewContent.ts
|
||||
var import_vue3 = require("vue");
|
||||
var NodeViewContent = (0, import_vue3.defineComponent)({
|
||||
name: "NodeViewContent",
|
||||
props: {
|
||||
as: {
|
||||
type: String,
|
||||
default: "div"
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return (0, import_vue3.h)(this.as, {
|
||||
style: {
|
||||
whiteSpace: "pre-wrap"
|
||||
},
|
||||
"data-node-view-content": ""
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// src/NodeViewWrapper.ts
|
||||
var import_vue4 = require("vue");
|
||||
var NodeViewWrapper = (0, import_vue4.defineComponent)({
|
||||
name: "NodeViewWrapper",
|
||||
props: {
|
||||
as: {
|
||||
type: String,
|
||||
default: "div"
|
||||
}
|
||||
},
|
||||
inject: ["onDragStart", "decorationClasses"],
|
||||
render() {
|
||||
var _a, _b;
|
||||
return (0, import_vue4.h)(
|
||||
this.as,
|
||||
{
|
||||
// @ts-ignore
|
||||
class: this.decorationClasses,
|
||||
style: {
|
||||
whiteSpace: "normal"
|
||||
},
|
||||
"data-node-view-wrapper": "",
|
||||
// @ts-ignore (https://github.com/vuejs/vue-next/issues/3031)
|
||||
onDragstart: this.onDragStart
|
||||
},
|
||||
(_b = (_a = this.$slots).default) == null ? void 0 : _b.call(_a)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// src/useEditor.ts
|
||||
var import_vue5 = require("vue");
|
||||
var useEditor = (options = {}) => {
|
||||
const editor = (0, import_vue5.shallowRef)();
|
||||
(0, import_vue5.onMounted)(() => {
|
||||
editor.value = new Editor(options);
|
||||
});
|
||||
(0, import_vue5.onBeforeUnmount)(() => {
|
||||
var _a, _b, _c, _d;
|
||||
const nodes = (_b = (_a = editor.value) == null ? void 0 : _a.view.dom) == null ? void 0 : _b.parentNode;
|
||||
const newEl = nodes == null ? void 0 : nodes.cloneNode(true);
|
||||
(_c = nodes == null ? void 0 : nodes.parentNode) == null ? void 0 : _c.replaceChild(newEl, nodes);
|
||||
(_d = editor.value) == null ? void 0 : _d.destroy();
|
||||
});
|
||||
return editor;
|
||||
};
|
||||
|
||||
// src/VueMarkViewRenderer.ts
|
||||
var import_core2 = require("@tiptap/core");
|
||||
var import_vue7 = require("vue");
|
||||
|
||||
// src/VueRenderer.ts
|
||||
var import_vue6 = require("vue");
|
||||
var VueRenderer = class {
|
||||
constructor(component, { props = {}, editor }) {
|
||||
/**
|
||||
* Flag to track if the renderer has been destroyed, preventing queued or asynchronous renders from executing after teardown.
|
||||
*/
|
||||
this.destroyed = false;
|
||||
this.editor = editor;
|
||||
this.component = (0, import_vue6.markRaw)(component);
|
||||
this.el = document.createElement("div");
|
||||
this.props = (0, import_vue6.reactive)(props);
|
||||
this.renderedComponent = this.renderComponent();
|
||||
}
|
||||
get element() {
|
||||
return this.renderedComponent.el;
|
||||
}
|
||||
get ref() {
|
||||
var _a, _b, _c, _d;
|
||||
if ((_b = (_a = this.renderedComponent.vNode) == null ? void 0 : _a.component) == null ? void 0 : _b.exposed) {
|
||||
return this.renderedComponent.vNode.component.exposed;
|
||||
}
|
||||
return (_d = (_c = this.renderedComponent.vNode) == null ? void 0 : _c.component) == null ? void 0 : _d.proxy;
|
||||
}
|
||||
renderComponent() {
|
||||
if (this.destroyed) {
|
||||
return this.renderedComponent;
|
||||
}
|
||||
let vNode = (0, import_vue6.h)(this.component, this.props);
|
||||
if (this.editor.appContext) {
|
||||
vNode.appContext = this.editor.appContext;
|
||||
}
|
||||
if (typeof document !== "undefined" && this.el) {
|
||||
(0, import_vue6.render)(vNode, this.el);
|
||||
}
|
||||
const destroy = () => {
|
||||
if (this.el) {
|
||||
(0, import_vue6.render)(null, this.el);
|
||||
}
|
||||
this.el = null;
|
||||
vNode = null;
|
||||
};
|
||||
return { vNode, destroy, el: this.el ? this.el.firstElementChild : null };
|
||||
}
|
||||
updateProps(props = {}) {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
Object.entries(props).forEach(([key, value]) => {
|
||||
this.props[key] = value;
|
||||
});
|
||||
this.renderComponent();
|
||||
}
|
||||
destroy() {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
this.destroyed = true;
|
||||
this.renderedComponent.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
// src/VueMarkViewRenderer.ts
|
||||
var markViewProps = {
|
||||
editor: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mark: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
extension: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
view: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
updateAttributes: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
HTMLAttributes: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
};
|
||||
var MarkViewContent = (0, import_vue7.defineComponent)({
|
||||
name: "MarkViewContent",
|
||||
props: {
|
||||
as: {
|
||||
type: String,
|
||||
default: "span"
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return (0, import_vue7.h)(this.as, {
|
||||
style: {
|
||||
whiteSpace: "inherit"
|
||||
},
|
||||
"data-mark-view-content": ""
|
||||
});
|
||||
}
|
||||
});
|
||||
var VueMarkView = class extends import_core2.MarkView {
|
||||
constructor(component, props, options) {
|
||||
super(component, props, options);
|
||||
const componentProps = { ...props, updateAttributes: this.updateAttributes.bind(this) };
|
||||
const extendedComponent = (0, import_vue7.defineComponent)({
|
||||
extends: { ...component },
|
||||
props: Object.keys(componentProps),
|
||||
template: this.component.template,
|
||||
setup: (reactiveProps) => {
|
||||
var _a;
|
||||
return (_a = component.setup) == null ? void 0 : _a.call(component, reactiveProps, {
|
||||
expose: () => void 0
|
||||
});
|
||||
},
|
||||
// Add support for scoped styles
|
||||
__scopeId: component.__scopeId,
|
||||
__cssModules: component.__cssModules,
|
||||
__name: component.__name,
|
||||
__file: component.__file
|
||||
});
|
||||
this.renderer = new VueRenderer(extendedComponent, {
|
||||
editor: this.editor,
|
||||
props: componentProps
|
||||
});
|
||||
}
|
||||
get dom() {
|
||||
return this.renderer.element;
|
||||
}
|
||||
get contentDOM() {
|
||||
return this.dom.querySelector("[data-mark-view-content]");
|
||||
}
|
||||
updateAttributes(attrs) {
|
||||
const unproxiedMark = (0, import_vue7.toRaw)(this.mark);
|
||||
super.updateAttributes(attrs, unproxiedMark);
|
||||
}
|
||||
destroy() {
|
||||
this.renderer.destroy();
|
||||
}
|
||||
};
|
||||
function VueMarkViewRenderer(component, options = {}) {
|
||||
return (props) => {
|
||||
if (!props.editor.contentComponent) {
|
||||
return {};
|
||||
}
|
||||
return new VueMarkView(component, props, options);
|
||||
};
|
||||
}
|
||||
|
||||
// src/VueNodeViewRenderer.ts
|
||||
var import_core3 = require("@tiptap/core");
|
||||
var import_vue8 = require("vue");
|
||||
var nodeViewProps = {
|
||||
editor: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
node: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
decorations: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
extension: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
getPos: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
updateAttributes: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
deleteNode: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
view: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
innerDecorations: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
HTMLAttributes: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
};
|
||||
var VueNodeView = class extends import_core3.NodeView {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.cachedExtensionWithSyncedStorage = null;
|
||||
}
|
||||
/**
|
||||
* Returns a proxy of the extension that redirects storage access to the editor's mutable storage.
|
||||
* This preserves the original prototype chain (instanceof checks, methods like configure/extend work).
|
||||
* Cached to avoid proxy creation on every update.
|
||||
*/
|
||||
get extensionWithSyncedStorage() {
|
||||
if (!this.cachedExtensionWithSyncedStorage) {
|
||||
const editor = this.editor;
|
||||
const extension = this.extension;
|
||||
this.cachedExtensionWithSyncedStorage = new Proxy(extension, {
|
||||
get(target, prop, receiver) {
|
||||
var _a;
|
||||
if (prop === "storage") {
|
||||
return (_a = editor.storage[extension.name]) != null ? _a : {};
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this.cachedExtensionWithSyncedStorage;
|
||||
}
|
||||
mount() {
|
||||
const props = {
|
||||
editor: this.editor,
|
||||
node: this.node,
|
||||
decorations: this.decorations,
|
||||
innerDecorations: this.innerDecorations,
|
||||
view: this.view,
|
||||
selected: false,
|
||||
extension: this.extensionWithSyncedStorage,
|
||||
HTMLAttributes: this.HTMLAttributes,
|
||||
getPos: () => this.getPos(),
|
||||
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
||||
deleteNode: () => this.deleteNode()
|
||||
};
|
||||
const onDragStart = this.onDragStart.bind(this);
|
||||
this.decorationClasses = (0, import_vue8.ref)(this.getDecorationClasses());
|
||||
const extendedComponent = (0, import_vue8.defineComponent)({
|
||||
extends: { ...this.component },
|
||||
props: Object.keys(props),
|
||||
template: this.component.template,
|
||||
setup: (reactiveProps) => {
|
||||
var _a, _b;
|
||||
(0, import_vue8.provide)("onDragStart", onDragStart);
|
||||
(0, import_vue8.provide)("decorationClasses", this.decorationClasses);
|
||||
return (_b = (_a = this.component).setup) == null ? void 0 : _b.call(_a, reactiveProps, {
|
||||
expose: () => void 0
|
||||
});
|
||||
},
|
||||
// add support for scoped styles
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__scopeId: this.component.__scopeId,
|
||||
// add support for CSS Modules
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__cssModules: this.component.__cssModules,
|
||||
// add support for vue devtools
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__name: this.component.__name,
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__file: this.component.__file
|
||||
});
|
||||
this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this);
|
||||
this.editor.on("selectionUpdate", this.handleSelectionUpdate);
|
||||
this.renderer = new VueRenderer(extendedComponent, {
|
||||
editor: this.editor,
|
||||
props
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Return the DOM element.
|
||||
* This is the element that will be used to display the node view.
|
||||
*/
|
||||
get dom() {
|
||||
if (!this.renderer.element || !this.renderer.element.hasAttribute("data-node-view-wrapper")) {
|
||||
throw Error("Please use the NodeViewWrapper component for your node view.");
|
||||
}
|
||||
return this.renderer.element;
|
||||
}
|
||||
/**
|
||||
* Return the content DOM element.
|
||||
* This is the element that will be used to display the rich-text content of the node.
|
||||
*/
|
||||
get contentDOM() {
|
||||
if (this.node.isLeaf) {
|
||||
return null;
|
||||
}
|
||||
return this.dom.querySelector("[data-node-view-content]");
|
||||
}
|
||||
/**
|
||||
* On editor selection update, check if the node is selected.
|
||||
* If it is, call `selectNode`, otherwise call `deselectNode`.
|
||||
*/
|
||||
handleSelectionUpdate() {
|
||||
const { from, to } = this.editor.state.selection;
|
||||
const pos = this.getPos();
|
||||
if (typeof pos !== "number") {
|
||||
return;
|
||||
}
|
||||
if (from <= pos && to >= pos + this.node.nodeSize) {
|
||||
if (this.renderer.props.selected) {
|
||||
return;
|
||||
}
|
||||
this.selectNode();
|
||||
} else {
|
||||
if (!this.renderer.props.selected) {
|
||||
return;
|
||||
}
|
||||
this.deselectNode();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* On update, update the React component.
|
||||
* To prevent unnecessary updates, the `update` option can be used.
|
||||
*/
|
||||
update(node, decorations, innerDecorations) {
|
||||
const rerenderComponent = (props) => {
|
||||
this.decorationClasses.value = this.getDecorationClasses();
|
||||
this.renderer.updateProps(props);
|
||||
};
|
||||
if (typeof this.options.update === "function") {
|
||||
const oldNode = this.node;
|
||||
const oldDecorations = this.decorations;
|
||||
const oldInnerDecorations = this.innerDecorations;
|
||||
this.node = node;
|
||||
this.decorations = decorations;
|
||||
this.innerDecorations = innerDecorations;
|
||||
return this.options.update({
|
||||
oldNode,
|
||||
oldDecorations,
|
||||
newNode: node,
|
||||
newDecorations: decorations,
|
||||
oldInnerDecorations,
|
||||
innerDecorations,
|
||||
updateProps: () => rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage })
|
||||
});
|
||||
}
|
||||
if (node.type !== this.node.type) {
|
||||
return false;
|
||||
}
|
||||
if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) {
|
||||
return true;
|
||||
}
|
||||
this.node = node;
|
||||
this.decorations = decorations;
|
||||
this.innerDecorations = innerDecorations;
|
||||
rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage });
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Select the node.
|
||||
* Add the `selected` prop and the `ProseMirror-selectednode` class.
|
||||
*/
|
||||
selectNode() {
|
||||
this.renderer.updateProps({
|
||||
selected: true
|
||||
});
|
||||
if (this.renderer.element) {
|
||||
this.renderer.element.classList.add("ProseMirror-selectednode");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Deselect the node.
|
||||
* Remove the `selected` prop and the `ProseMirror-selectednode` class.
|
||||
*/
|
||||
deselectNode() {
|
||||
this.renderer.updateProps({
|
||||
selected: false
|
||||
});
|
||||
if (this.renderer.element) {
|
||||
this.renderer.element.classList.remove("ProseMirror-selectednode");
|
||||
}
|
||||
}
|
||||
getDecorationClasses() {
|
||||
return this.decorations.flatMap((item) => item.type.attrs.class).join(" ");
|
||||
}
|
||||
destroy() {
|
||||
this.renderer.destroy();
|
||||
this.editor.off("selectionUpdate", this.handleSelectionUpdate);
|
||||
}
|
||||
};
|
||||
function VueNodeViewRenderer(component, options) {
|
||||
return (props) => {
|
||||
if (!props.editor.contentComponent) {
|
||||
return {};
|
||||
}
|
||||
const normalizedComponent = typeof component === "function" && "__vccOpts" in component ? component.__vccOpts : component;
|
||||
return new VueNodeView(normalizedComponent, props, options);
|
||||
};
|
||||
}
|
||||
|
||||
// src/index.ts
|
||||
__reExport(index_exports, require("@tiptap/core"), module.exports);
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
Editor,
|
||||
EditorContent,
|
||||
MarkViewContent,
|
||||
NodeViewContent,
|
||||
NodeViewWrapper,
|
||||
VueMarkView,
|
||||
VueMarkViewRenderer,
|
||||
VueNodeViewRenderer,
|
||||
VueRenderer,
|
||||
markViewProps,
|
||||
nodeViewProps,
|
||||
useEditor,
|
||||
...require("@tiptap/core")
|
||||
});
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
1
node_modules/@tiptap/vue-3/dist/index.cjs.map
generated
vendored
Normal file
1
node_modules/@tiptap/vue-3/dist/index.cjs.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
226
node_modules/@tiptap/vue-3/dist/index.d.cts
generated
vendored
Normal file
226
node_modules/@tiptap/vue-3/dist/index.d.cts
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
import { Editor as Editor$1, EditorOptions, Storage, MarkViewRendererOptions, MarkViewProps, MarkView, MarkViewRenderer, NodeViewProps, NodeViewRendererOptions, NodeViewRenderer } from '@tiptap/core';
|
||||
export * from '@tiptap/core';
|
||||
import { EditorState, Plugin, PluginKey } from '@tiptap/pm/state';
|
||||
import * as vue from 'vue';
|
||||
import { ComponentInternalInstance, ComponentPublicInstance, AppContext, PropType, Ref, h, Component } from 'vue';
|
||||
import { Node } from '@tiptap/pm/model';
|
||||
import { Decoration, DecorationSource } from '@tiptap/pm/view';
|
||||
|
||||
type ContentComponent = ComponentInternalInstance & {
|
||||
ctx: ComponentPublicInstance;
|
||||
};
|
||||
declare class Editor extends Editor$1 {
|
||||
private reactiveState;
|
||||
private reactiveExtensionStorage;
|
||||
contentComponent: ContentComponent | null;
|
||||
appContext: AppContext | null;
|
||||
constructor(options?: Partial<EditorOptions>);
|
||||
get state(): EditorState;
|
||||
get storage(): Storage;
|
||||
/**
|
||||
* Register a ProseMirror plugin.
|
||||
*/
|
||||
registerPlugin(plugin: Plugin, handlePlugins?: (newPlugin: Plugin, plugins: Plugin[]) => Plugin[]): EditorState;
|
||||
/**
|
||||
* Unregister a ProseMirror plugin.
|
||||
*/
|
||||
unregisterPlugin(nameOrPluginKey: string | PluginKey): EditorState | undefined;
|
||||
}
|
||||
|
||||
declare const EditorContent: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
editor: {
|
||||
default: null;
|
||||
type: PropType<Editor>;
|
||||
};
|
||||
}>, {
|
||||
rootEl: Ref<Element | undefined, Element | undefined>;
|
||||
}, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
editor: {
|
||||
default: null;
|
||||
type: PropType<Editor>;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
editor: Editor;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
|
||||
declare const NodeViewContent: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>, {}, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
as: string;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
|
||||
declare const NodeViewWrapper: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>, {}, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
as: string;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
|
||||
declare const useEditor: (options?: Partial<EditorOptions>) => vue.ShallowRef<Editor | undefined, Editor | undefined>;
|
||||
|
||||
interface VueRendererOptions {
|
||||
editor: Editor$1;
|
||||
props?: Record<string, any>;
|
||||
}
|
||||
type ExtendedVNode = ReturnType<typeof h> | null;
|
||||
interface RenderedComponent {
|
||||
vNode: ExtendedVNode;
|
||||
destroy: () => void;
|
||||
el: Element | null;
|
||||
}
|
||||
/**
|
||||
* This class is used to render Vue components inside the editor.
|
||||
*/
|
||||
declare class VueRenderer {
|
||||
renderedComponent: RenderedComponent;
|
||||
editor: Editor;
|
||||
component: Component;
|
||||
el: Element | null;
|
||||
props: Record<string, any>;
|
||||
/**
|
||||
* Flag to track if the renderer has been destroyed, preventing queued or asynchronous renders from executing after teardown.
|
||||
*/
|
||||
destroyed: boolean;
|
||||
constructor(component: Component, { props, editor }: VueRendererOptions);
|
||||
get element(): Element | null;
|
||||
get ref(): any;
|
||||
renderComponent(): RenderedComponent;
|
||||
updateProps(props?: Record<string, any>): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
interface VueMarkViewRendererOptions extends MarkViewRendererOptions {
|
||||
as?: string;
|
||||
className?: string;
|
||||
attrs?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
declare const markViewProps: {
|
||||
editor: {
|
||||
type: PropType<MarkViewProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
mark: {
|
||||
type: PropType<MarkViewProps["mark"]>;
|
||||
required: true;
|
||||
};
|
||||
extension: {
|
||||
type: PropType<MarkViewProps["extension"]>;
|
||||
required: true;
|
||||
};
|
||||
inline: {
|
||||
type: PropType<MarkViewProps["inline"]>;
|
||||
required: true;
|
||||
};
|
||||
view: {
|
||||
type: PropType<MarkViewProps["view"]>;
|
||||
required: true;
|
||||
};
|
||||
updateAttributes: {
|
||||
type: PropType<MarkViewProps["updateAttributes"]>;
|
||||
required: true;
|
||||
};
|
||||
HTMLAttributes: {
|
||||
type: PropType<MarkViewProps["HTMLAttributes"]>;
|
||||
required: true;
|
||||
};
|
||||
};
|
||||
declare const MarkViewContent: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>, {}, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
as: string;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
declare class VueMarkView extends MarkView<Component, VueMarkViewRendererOptions> {
|
||||
renderer: VueRenderer;
|
||||
constructor(component: Component, props: MarkViewProps, options?: Partial<VueMarkViewRendererOptions>);
|
||||
get dom(): HTMLElement;
|
||||
get contentDOM(): HTMLElement | null;
|
||||
updateAttributes(attrs: Record<string, any>): void;
|
||||
destroy(): void;
|
||||
}
|
||||
declare function VueMarkViewRenderer(component: Component, options?: Partial<VueMarkViewRendererOptions>): MarkViewRenderer;
|
||||
|
||||
declare const nodeViewProps: {
|
||||
editor: {
|
||||
type: PropType<NodeViewProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
node: {
|
||||
type: PropType<NodeViewProps["node"]>;
|
||||
required: true;
|
||||
};
|
||||
decorations: {
|
||||
type: PropType<NodeViewProps["decorations"]>;
|
||||
required: true;
|
||||
};
|
||||
selected: {
|
||||
type: PropType<NodeViewProps["selected"]>;
|
||||
required: true;
|
||||
};
|
||||
extension: {
|
||||
type: PropType<NodeViewProps["extension"]>;
|
||||
required: true;
|
||||
};
|
||||
getPos: {
|
||||
type: PropType<NodeViewProps["getPos"]>;
|
||||
required: true;
|
||||
};
|
||||
updateAttributes: {
|
||||
type: PropType<NodeViewProps["updateAttributes"]>;
|
||||
required: true;
|
||||
};
|
||||
deleteNode: {
|
||||
type: PropType<NodeViewProps["deleteNode"]>;
|
||||
required: true;
|
||||
};
|
||||
view: {
|
||||
type: PropType<NodeViewProps["view"]>;
|
||||
required: true;
|
||||
};
|
||||
innerDecorations: {
|
||||
type: PropType<NodeViewProps["innerDecorations"]>;
|
||||
required: true;
|
||||
};
|
||||
HTMLAttributes: {
|
||||
type: PropType<NodeViewProps["HTMLAttributes"]>;
|
||||
required: true;
|
||||
};
|
||||
};
|
||||
interface VueNodeViewRendererOptions extends NodeViewRendererOptions {
|
||||
update: ((props: {
|
||||
oldNode: Node;
|
||||
oldDecorations: readonly Decoration[];
|
||||
oldInnerDecorations: DecorationSource;
|
||||
newNode: Node;
|
||||
newDecorations: readonly Decoration[];
|
||||
innerDecorations: DecorationSource;
|
||||
updateProps: () => void;
|
||||
}) => boolean) | null;
|
||||
}
|
||||
declare function VueNodeViewRenderer(component: Component<NodeViewProps>, options?: Partial<VueNodeViewRendererOptions>): NodeViewRenderer;
|
||||
|
||||
export { Editor, EditorContent, MarkViewContent, NodeViewContent, NodeViewWrapper, VueMarkView, VueMarkViewRenderer, type VueMarkViewRendererOptions, VueNodeViewRenderer, type VueNodeViewRendererOptions, VueRenderer, type VueRendererOptions, markViewProps, nodeViewProps, useEditor };
|
||||
226
node_modules/@tiptap/vue-3/dist/index.d.ts
generated
vendored
Normal file
226
node_modules/@tiptap/vue-3/dist/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
import { Editor as Editor$1, EditorOptions, Storage, MarkViewRendererOptions, MarkViewProps, MarkView, MarkViewRenderer, NodeViewProps, NodeViewRendererOptions, NodeViewRenderer } from '@tiptap/core';
|
||||
export * from '@tiptap/core';
|
||||
import { EditorState, Plugin, PluginKey } from '@tiptap/pm/state';
|
||||
import * as vue from 'vue';
|
||||
import { ComponentInternalInstance, ComponentPublicInstance, AppContext, PropType, Ref, h, Component } from 'vue';
|
||||
import { Node } from '@tiptap/pm/model';
|
||||
import { Decoration, DecorationSource } from '@tiptap/pm/view';
|
||||
|
||||
type ContentComponent = ComponentInternalInstance & {
|
||||
ctx: ComponentPublicInstance;
|
||||
};
|
||||
declare class Editor extends Editor$1 {
|
||||
private reactiveState;
|
||||
private reactiveExtensionStorage;
|
||||
contentComponent: ContentComponent | null;
|
||||
appContext: AppContext | null;
|
||||
constructor(options?: Partial<EditorOptions>);
|
||||
get state(): EditorState;
|
||||
get storage(): Storage;
|
||||
/**
|
||||
* Register a ProseMirror plugin.
|
||||
*/
|
||||
registerPlugin(plugin: Plugin, handlePlugins?: (newPlugin: Plugin, plugins: Plugin[]) => Plugin[]): EditorState;
|
||||
/**
|
||||
* Unregister a ProseMirror plugin.
|
||||
*/
|
||||
unregisterPlugin(nameOrPluginKey: string | PluginKey): EditorState | undefined;
|
||||
}
|
||||
|
||||
declare const EditorContent: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
editor: {
|
||||
default: null;
|
||||
type: PropType<Editor>;
|
||||
};
|
||||
}>, {
|
||||
rootEl: Ref<Element | undefined, Element | undefined>;
|
||||
}, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
editor: {
|
||||
default: null;
|
||||
type: PropType<Editor>;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
editor: Editor;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
|
||||
declare const NodeViewContent: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>, {}, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
as: string;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
|
||||
declare const NodeViewWrapper: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>, {}, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
as: string;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
|
||||
declare const useEditor: (options?: Partial<EditorOptions>) => vue.ShallowRef<Editor | undefined, Editor | undefined>;
|
||||
|
||||
interface VueRendererOptions {
|
||||
editor: Editor$1;
|
||||
props?: Record<string, any>;
|
||||
}
|
||||
type ExtendedVNode = ReturnType<typeof h> | null;
|
||||
interface RenderedComponent {
|
||||
vNode: ExtendedVNode;
|
||||
destroy: () => void;
|
||||
el: Element | null;
|
||||
}
|
||||
/**
|
||||
* This class is used to render Vue components inside the editor.
|
||||
*/
|
||||
declare class VueRenderer {
|
||||
renderedComponent: RenderedComponent;
|
||||
editor: Editor;
|
||||
component: Component;
|
||||
el: Element | null;
|
||||
props: Record<string, any>;
|
||||
/**
|
||||
* Flag to track if the renderer has been destroyed, preventing queued or asynchronous renders from executing after teardown.
|
||||
*/
|
||||
destroyed: boolean;
|
||||
constructor(component: Component, { props, editor }: VueRendererOptions);
|
||||
get element(): Element | null;
|
||||
get ref(): any;
|
||||
renderComponent(): RenderedComponent;
|
||||
updateProps(props?: Record<string, any>): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
interface VueMarkViewRendererOptions extends MarkViewRendererOptions {
|
||||
as?: string;
|
||||
className?: string;
|
||||
attrs?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
declare const markViewProps: {
|
||||
editor: {
|
||||
type: PropType<MarkViewProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
mark: {
|
||||
type: PropType<MarkViewProps["mark"]>;
|
||||
required: true;
|
||||
};
|
||||
extension: {
|
||||
type: PropType<MarkViewProps["extension"]>;
|
||||
required: true;
|
||||
};
|
||||
inline: {
|
||||
type: PropType<MarkViewProps["inline"]>;
|
||||
required: true;
|
||||
};
|
||||
view: {
|
||||
type: PropType<MarkViewProps["view"]>;
|
||||
required: true;
|
||||
};
|
||||
updateAttributes: {
|
||||
type: PropType<MarkViewProps["updateAttributes"]>;
|
||||
required: true;
|
||||
};
|
||||
HTMLAttributes: {
|
||||
type: PropType<MarkViewProps["HTMLAttributes"]>;
|
||||
required: true;
|
||||
};
|
||||
};
|
||||
declare const MarkViewContent: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>, {}, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
as: string;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
declare class VueMarkView extends MarkView<Component, VueMarkViewRendererOptions> {
|
||||
renderer: VueRenderer;
|
||||
constructor(component: Component, props: MarkViewProps, options?: Partial<VueMarkViewRendererOptions>);
|
||||
get dom(): HTMLElement;
|
||||
get contentDOM(): HTMLElement | null;
|
||||
updateAttributes(attrs: Record<string, any>): void;
|
||||
destroy(): void;
|
||||
}
|
||||
declare function VueMarkViewRenderer(component: Component, options?: Partial<VueMarkViewRendererOptions>): MarkViewRenderer;
|
||||
|
||||
declare const nodeViewProps: {
|
||||
editor: {
|
||||
type: PropType<NodeViewProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
node: {
|
||||
type: PropType<NodeViewProps["node"]>;
|
||||
required: true;
|
||||
};
|
||||
decorations: {
|
||||
type: PropType<NodeViewProps["decorations"]>;
|
||||
required: true;
|
||||
};
|
||||
selected: {
|
||||
type: PropType<NodeViewProps["selected"]>;
|
||||
required: true;
|
||||
};
|
||||
extension: {
|
||||
type: PropType<NodeViewProps["extension"]>;
|
||||
required: true;
|
||||
};
|
||||
getPos: {
|
||||
type: PropType<NodeViewProps["getPos"]>;
|
||||
required: true;
|
||||
};
|
||||
updateAttributes: {
|
||||
type: PropType<NodeViewProps["updateAttributes"]>;
|
||||
required: true;
|
||||
};
|
||||
deleteNode: {
|
||||
type: PropType<NodeViewProps["deleteNode"]>;
|
||||
required: true;
|
||||
};
|
||||
view: {
|
||||
type: PropType<NodeViewProps["view"]>;
|
||||
required: true;
|
||||
};
|
||||
innerDecorations: {
|
||||
type: PropType<NodeViewProps["innerDecorations"]>;
|
||||
required: true;
|
||||
};
|
||||
HTMLAttributes: {
|
||||
type: PropType<NodeViewProps["HTMLAttributes"]>;
|
||||
required: true;
|
||||
};
|
||||
};
|
||||
interface VueNodeViewRendererOptions extends NodeViewRendererOptions {
|
||||
update: ((props: {
|
||||
oldNode: Node;
|
||||
oldDecorations: readonly Decoration[];
|
||||
oldInnerDecorations: DecorationSource;
|
||||
newNode: Node;
|
||||
newDecorations: readonly Decoration[];
|
||||
innerDecorations: DecorationSource;
|
||||
updateProps: () => void;
|
||||
}) => boolean) | null;
|
||||
}
|
||||
declare function VueNodeViewRenderer(component: Component<NodeViewProps>, options?: Partial<VueNodeViewRendererOptions>): NodeViewRenderer;
|
||||
|
||||
export { Editor, EditorContent, MarkViewContent, NodeViewContent, NodeViewWrapper, VueMarkView, VueMarkViewRenderer, type VueMarkViewRendererOptions, VueNodeViewRenderer, type VueNodeViewRendererOptions, VueRenderer, type VueRendererOptions, markViewProps, nodeViewProps, useEditor };
|
||||
615
node_modules/@tiptap/vue-3/dist/index.js
generated
vendored
Normal file
615
node_modules/@tiptap/vue-3/dist/index.js
generated
vendored
Normal file
@@ -0,0 +1,615 @@
|
||||
// src/Editor.ts
|
||||
import { Editor as CoreEditor } from "@tiptap/core";
|
||||
import { customRef, markRaw } from "vue";
|
||||
function useDebouncedRef(value) {
|
||||
return customRef((track, trigger) => {
|
||||
return {
|
||||
get() {
|
||||
track();
|
||||
return value;
|
||||
},
|
||||
set(newValue) {
|
||||
value = newValue;
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
trigger();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
var Editor = class extends CoreEditor {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.contentComponent = null;
|
||||
this.appContext = null;
|
||||
this.reactiveState = useDebouncedRef(this.view.state);
|
||||
this.reactiveExtensionStorage = useDebouncedRef(this.extensionStorage);
|
||||
this.on("beforeTransaction", ({ nextState }) => {
|
||||
this.reactiveState.value = nextState;
|
||||
this.reactiveExtensionStorage.value = this.extensionStorage;
|
||||
});
|
||||
return markRaw(this);
|
||||
}
|
||||
get state() {
|
||||
return this.reactiveState ? this.reactiveState.value : this.view.state;
|
||||
}
|
||||
get storage() {
|
||||
return this.reactiveExtensionStorage ? this.reactiveExtensionStorage.value : super.storage;
|
||||
}
|
||||
/**
|
||||
* Register a ProseMirror plugin.
|
||||
*/
|
||||
registerPlugin(plugin, handlePlugins) {
|
||||
const nextState = super.registerPlugin(plugin, handlePlugins);
|
||||
if (this.reactiveState) {
|
||||
this.reactiveState.value = nextState;
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
/**
|
||||
* Unregister a ProseMirror plugin.
|
||||
*/
|
||||
unregisterPlugin(nameOrPluginKey) {
|
||||
const nextState = super.unregisterPlugin(nameOrPluginKey);
|
||||
if (this.reactiveState && nextState) {
|
||||
this.reactiveState.value = nextState;
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
};
|
||||
|
||||
// src/EditorContent.ts
|
||||
import { defineComponent, getCurrentInstance, h, nextTick, onBeforeUnmount, ref, unref, watchEffect } from "vue";
|
||||
var EditorContent = defineComponent({
|
||||
name: "EditorContent",
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const rootEl = ref();
|
||||
const instance = getCurrentInstance();
|
||||
watchEffect(() => {
|
||||
const editor = props.editor;
|
||||
if (editor && editor.options.element && rootEl.value) {
|
||||
nextTick(() => {
|
||||
var _a;
|
||||
if (!rootEl.value || !((_a = editor.view.dom) == null ? void 0 : _a.parentNode)) {
|
||||
return;
|
||||
}
|
||||
const element = unref(rootEl.value);
|
||||
rootEl.value.append(...editor.view.dom.parentNode.childNodes);
|
||||
editor.contentComponent = instance.ctx._;
|
||||
if (instance) {
|
||||
editor.appContext = {
|
||||
...instance.appContext,
|
||||
// Vue internally uses prototype chain to forward/shadow injects across the entire component chain
|
||||
// so don't use object spread operator or 'Object.assign' and just set `provides` as is on editor's appContext
|
||||
// @ts-expect-error forward instance's 'provides' into appContext
|
||||
provides: instance.provides
|
||||
};
|
||||
}
|
||||
editor.setOptions({
|
||||
element
|
||||
});
|
||||
editor.createNodeViews();
|
||||
});
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
const editor = props.editor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
editor.contentComponent = null;
|
||||
editor.appContext = null;
|
||||
});
|
||||
return { rootEl };
|
||||
},
|
||||
render() {
|
||||
return h("div", {
|
||||
ref: (el) => {
|
||||
this.rootEl = el;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// src/NodeViewContent.ts
|
||||
import { defineComponent as defineComponent2, h as h2 } from "vue";
|
||||
var NodeViewContent = defineComponent2({
|
||||
name: "NodeViewContent",
|
||||
props: {
|
||||
as: {
|
||||
type: String,
|
||||
default: "div"
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return h2(this.as, {
|
||||
style: {
|
||||
whiteSpace: "pre-wrap"
|
||||
},
|
||||
"data-node-view-content": ""
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// src/NodeViewWrapper.ts
|
||||
import { defineComponent as defineComponent3, h as h3 } from "vue";
|
||||
var NodeViewWrapper = defineComponent3({
|
||||
name: "NodeViewWrapper",
|
||||
props: {
|
||||
as: {
|
||||
type: String,
|
||||
default: "div"
|
||||
}
|
||||
},
|
||||
inject: ["onDragStart", "decorationClasses"],
|
||||
render() {
|
||||
var _a, _b;
|
||||
return h3(
|
||||
this.as,
|
||||
{
|
||||
// @ts-ignore
|
||||
class: this.decorationClasses,
|
||||
style: {
|
||||
whiteSpace: "normal"
|
||||
},
|
||||
"data-node-view-wrapper": "",
|
||||
// @ts-ignore (https://github.com/vuejs/vue-next/issues/3031)
|
||||
onDragstart: this.onDragStart
|
||||
},
|
||||
(_b = (_a = this.$slots).default) == null ? void 0 : _b.call(_a)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// src/useEditor.ts
|
||||
import { onBeforeUnmount as onBeforeUnmount2, onMounted, shallowRef } from "vue";
|
||||
var useEditor = (options = {}) => {
|
||||
const editor = shallowRef();
|
||||
onMounted(() => {
|
||||
editor.value = new Editor(options);
|
||||
});
|
||||
onBeforeUnmount2(() => {
|
||||
var _a, _b, _c, _d;
|
||||
const nodes = (_b = (_a = editor.value) == null ? void 0 : _a.view.dom) == null ? void 0 : _b.parentNode;
|
||||
const newEl = nodes == null ? void 0 : nodes.cloneNode(true);
|
||||
(_c = nodes == null ? void 0 : nodes.parentNode) == null ? void 0 : _c.replaceChild(newEl, nodes);
|
||||
(_d = editor.value) == null ? void 0 : _d.destroy();
|
||||
});
|
||||
return editor;
|
||||
};
|
||||
|
||||
// src/VueMarkViewRenderer.ts
|
||||
import { MarkView } from "@tiptap/core";
|
||||
import { defineComponent as defineComponent4, h as h5, toRaw } from "vue";
|
||||
|
||||
// src/VueRenderer.ts
|
||||
import { h as h4, markRaw as markRaw2, reactive, render } from "vue";
|
||||
var VueRenderer = class {
|
||||
constructor(component, { props = {}, editor }) {
|
||||
/**
|
||||
* Flag to track if the renderer has been destroyed, preventing queued or asynchronous renders from executing after teardown.
|
||||
*/
|
||||
this.destroyed = false;
|
||||
this.editor = editor;
|
||||
this.component = markRaw2(component);
|
||||
this.el = document.createElement("div");
|
||||
this.props = reactive(props);
|
||||
this.renderedComponent = this.renderComponent();
|
||||
}
|
||||
get element() {
|
||||
return this.renderedComponent.el;
|
||||
}
|
||||
get ref() {
|
||||
var _a, _b, _c, _d;
|
||||
if ((_b = (_a = this.renderedComponent.vNode) == null ? void 0 : _a.component) == null ? void 0 : _b.exposed) {
|
||||
return this.renderedComponent.vNode.component.exposed;
|
||||
}
|
||||
return (_d = (_c = this.renderedComponent.vNode) == null ? void 0 : _c.component) == null ? void 0 : _d.proxy;
|
||||
}
|
||||
renderComponent() {
|
||||
if (this.destroyed) {
|
||||
return this.renderedComponent;
|
||||
}
|
||||
let vNode = h4(this.component, this.props);
|
||||
if (this.editor.appContext) {
|
||||
vNode.appContext = this.editor.appContext;
|
||||
}
|
||||
if (typeof document !== "undefined" && this.el) {
|
||||
render(vNode, this.el);
|
||||
}
|
||||
const destroy = () => {
|
||||
if (this.el) {
|
||||
render(null, this.el);
|
||||
}
|
||||
this.el = null;
|
||||
vNode = null;
|
||||
};
|
||||
return { vNode, destroy, el: this.el ? this.el.firstElementChild : null };
|
||||
}
|
||||
updateProps(props = {}) {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
Object.entries(props).forEach(([key, value]) => {
|
||||
this.props[key] = value;
|
||||
});
|
||||
this.renderComponent();
|
||||
}
|
||||
destroy() {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
this.destroyed = true;
|
||||
this.renderedComponent.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
// src/VueMarkViewRenderer.ts
|
||||
var markViewProps = {
|
||||
editor: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mark: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
extension: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
view: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
updateAttributes: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
HTMLAttributes: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
};
|
||||
var MarkViewContent = defineComponent4({
|
||||
name: "MarkViewContent",
|
||||
props: {
|
||||
as: {
|
||||
type: String,
|
||||
default: "span"
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return h5(this.as, {
|
||||
style: {
|
||||
whiteSpace: "inherit"
|
||||
},
|
||||
"data-mark-view-content": ""
|
||||
});
|
||||
}
|
||||
});
|
||||
var VueMarkView = class extends MarkView {
|
||||
constructor(component, props, options) {
|
||||
super(component, props, options);
|
||||
const componentProps = { ...props, updateAttributes: this.updateAttributes.bind(this) };
|
||||
const extendedComponent = defineComponent4({
|
||||
extends: { ...component },
|
||||
props: Object.keys(componentProps),
|
||||
template: this.component.template,
|
||||
setup: (reactiveProps) => {
|
||||
var _a;
|
||||
return (_a = component.setup) == null ? void 0 : _a.call(component, reactiveProps, {
|
||||
expose: () => void 0
|
||||
});
|
||||
},
|
||||
// Add support for scoped styles
|
||||
__scopeId: component.__scopeId,
|
||||
__cssModules: component.__cssModules,
|
||||
__name: component.__name,
|
||||
__file: component.__file
|
||||
});
|
||||
this.renderer = new VueRenderer(extendedComponent, {
|
||||
editor: this.editor,
|
||||
props: componentProps
|
||||
});
|
||||
}
|
||||
get dom() {
|
||||
return this.renderer.element;
|
||||
}
|
||||
get contentDOM() {
|
||||
return this.dom.querySelector("[data-mark-view-content]");
|
||||
}
|
||||
updateAttributes(attrs) {
|
||||
const unproxiedMark = toRaw(this.mark);
|
||||
super.updateAttributes(attrs, unproxiedMark);
|
||||
}
|
||||
destroy() {
|
||||
this.renderer.destroy();
|
||||
}
|
||||
};
|
||||
function VueMarkViewRenderer(component, options = {}) {
|
||||
return (props) => {
|
||||
if (!props.editor.contentComponent) {
|
||||
return {};
|
||||
}
|
||||
return new VueMarkView(component, props, options);
|
||||
};
|
||||
}
|
||||
|
||||
// src/VueNodeViewRenderer.ts
|
||||
import { NodeView } from "@tiptap/core";
|
||||
import { defineComponent as defineComponent5, provide, ref as ref2 } from "vue";
|
||||
var nodeViewProps = {
|
||||
editor: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
node: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
decorations: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
extension: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
getPos: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
updateAttributes: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
deleteNode: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
view: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
innerDecorations: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
HTMLAttributes: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
};
|
||||
var VueNodeView = class extends NodeView {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.cachedExtensionWithSyncedStorage = null;
|
||||
}
|
||||
/**
|
||||
* Returns a proxy of the extension that redirects storage access to the editor's mutable storage.
|
||||
* This preserves the original prototype chain (instanceof checks, methods like configure/extend work).
|
||||
* Cached to avoid proxy creation on every update.
|
||||
*/
|
||||
get extensionWithSyncedStorage() {
|
||||
if (!this.cachedExtensionWithSyncedStorage) {
|
||||
const editor = this.editor;
|
||||
const extension = this.extension;
|
||||
this.cachedExtensionWithSyncedStorage = new Proxy(extension, {
|
||||
get(target, prop, receiver) {
|
||||
var _a;
|
||||
if (prop === "storage") {
|
||||
return (_a = editor.storage[extension.name]) != null ? _a : {};
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this.cachedExtensionWithSyncedStorage;
|
||||
}
|
||||
mount() {
|
||||
const props = {
|
||||
editor: this.editor,
|
||||
node: this.node,
|
||||
decorations: this.decorations,
|
||||
innerDecorations: this.innerDecorations,
|
||||
view: this.view,
|
||||
selected: false,
|
||||
extension: this.extensionWithSyncedStorage,
|
||||
HTMLAttributes: this.HTMLAttributes,
|
||||
getPos: () => this.getPos(),
|
||||
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
||||
deleteNode: () => this.deleteNode()
|
||||
};
|
||||
const onDragStart = this.onDragStart.bind(this);
|
||||
this.decorationClasses = ref2(this.getDecorationClasses());
|
||||
const extendedComponent = defineComponent5({
|
||||
extends: { ...this.component },
|
||||
props: Object.keys(props),
|
||||
template: this.component.template,
|
||||
setup: (reactiveProps) => {
|
||||
var _a, _b;
|
||||
provide("onDragStart", onDragStart);
|
||||
provide("decorationClasses", this.decorationClasses);
|
||||
return (_b = (_a = this.component).setup) == null ? void 0 : _b.call(_a, reactiveProps, {
|
||||
expose: () => void 0
|
||||
});
|
||||
},
|
||||
// add support for scoped styles
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__scopeId: this.component.__scopeId,
|
||||
// add support for CSS Modules
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__cssModules: this.component.__cssModules,
|
||||
// add support for vue devtools
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__name: this.component.__name,
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__file: this.component.__file
|
||||
});
|
||||
this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this);
|
||||
this.editor.on("selectionUpdate", this.handleSelectionUpdate);
|
||||
this.renderer = new VueRenderer(extendedComponent, {
|
||||
editor: this.editor,
|
||||
props
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Return the DOM element.
|
||||
* This is the element that will be used to display the node view.
|
||||
*/
|
||||
get dom() {
|
||||
if (!this.renderer.element || !this.renderer.element.hasAttribute("data-node-view-wrapper")) {
|
||||
throw Error("Please use the NodeViewWrapper component for your node view.");
|
||||
}
|
||||
return this.renderer.element;
|
||||
}
|
||||
/**
|
||||
* Return the content DOM element.
|
||||
* This is the element that will be used to display the rich-text content of the node.
|
||||
*/
|
||||
get contentDOM() {
|
||||
if (this.node.isLeaf) {
|
||||
return null;
|
||||
}
|
||||
return this.dom.querySelector("[data-node-view-content]");
|
||||
}
|
||||
/**
|
||||
* On editor selection update, check if the node is selected.
|
||||
* If it is, call `selectNode`, otherwise call `deselectNode`.
|
||||
*/
|
||||
handleSelectionUpdate() {
|
||||
const { from, to } = this.editor.state.selection;
|
||||
const pos = this.getPos();
|
||||
if (typeof pos !== "number") {
|
||||
return;
|
||||
}
|
||||
if (from <= pos && to >= pos + this.node.nodeSize) {
|
||||
if (this.renderer.props.selected) {
|
||||
return;
|
||||
}
|
||||
this.selectNode();
|
||||
} else {
|
||||
if (!this.renderer.props.selected) {
|
||||
return;
|
||||
}
|
||||
this.deselectNode();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* On update, update the React component.
|
||||
* To prevent unnecessary updates, the `update` option can be used.
|
||||
*/
|
||||
update(node, decorations, innerDecorations) {
|
||||
const rerenderComponent = (props) => {
|
||||
this.decorationClasses.value = this.getDecorationClasses();
|
||||
this.renderer.updateProps(props);
|
||||
};
|
||||
if (typeof this.options.update === "function") {
|
||||
const oldNode = this.node;
|
||||
const oldDecorations = this.decorations;
|
||||
const oldInnerDecorations = this.innerDecorations;
|
||||
this.node = node;
|
||||
this.decorations = decorations;
|
||||
this.innerDecorations = innerDecorations;
|
||||
return this.options.update({
|
||||
oldNode,
|
||||
oldDecorations,
|
||||
newNode: node,
|
||||
newDecorations: decorations,
|
||||
oldInnerDecorations,
|
||||
innerDecorations,
|
||||
updateProps: () => rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage })
|
||||
});
|
||||
}
|
||||
if (node.type !== this.node.type) {
|
||||
return false;
|
||||
}
|
||||
if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) {
|
||||
return true;
|
||||
}
|
||||
this.node = node;
|
||||
this.decorations = decorations;
|
||||
this.innerDecorations = innerDecorations;
|
||||
rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage });
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Select the node.
|
||||
* Add the `selected` prop and the `ProseMirror-selectednode` class.
|
||||
*/
|
||||
selectNode() {
|
||||
this.renderer.updateProps({
|
||||
selected: true
|
||||
});
|
||||
if (this.renderer.element) {
|
||||
this.renderer.element.classList.add("ProseMirror-selectednode");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Deselect the node.
|
||||
* Remove the `selected` prop and the `ProseMirror-selectednode` class.
|
||||
*/
|
||||
deselectNode() {
|
||||
this.renderer.updateProps({
|
||||
selected: false
|
||||
});
|
||||
if (this.renderer.element) {
|
||||
this.renderer.element.classList.remove("ProseMirror-selectednode");
|
||||
}
|
||||
}
|
||||
getDecorationClasses() {
|
||||
return this.decorations.flatMap((item) => item.type.attrs.class).join(" ");
|
||||
}
|
||||
destroy() {
|
||||
this.renderer.destroy();
|
||||
this.editor.off("selectionUpdate", this.handleSelectionUpdate);
|
||||
}
|
||||
};
|
||||
function VueNodeViewRenderer(component, options) {
|
||||
return (props) => {
|
||||
if (!props.editor.contentComponent) {
|
||||
return {};
|
||||
}
|
||||
const normalizedComponent = typeof component === "function" && "__vccOpts" in component ? component.__vccOpts : component;
|
||||
return new VueNodeView(normalizedComponent, props, options);
|
||||
};
|
||||
}
|
||||
|
||||
// src/index.ts
|
||||
export * from "@tiptap/core";
|
||||
export {
|
||||
Editor,
|
||||
EditorContent,
|
||||
MarkViewContent,
|
||||
NodeViewContent,
|
||||
NodeViewWrapper,
|
||||
VueMarkView,
|
||||
VueMarkViewRenderer,
|
||||
VueNodeViewRenderer,
|
||||
VueRenderer,
|
||||
markViewProps,
|
||||
nodeViewProps,
|
||||
useEditor
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
node_modules/@tiptap/vue-3/dist/index.js.map
generated
vendored
Normal file
1
node_modules/@tiptap/vue-3/dist/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
873
node_modules/@tiptap/vue-3/dist/menus/index.cjs
generated
vendored
Normal file
873
node_modules/@tiptap/vue-3/dist/menus/index.cjs
generated
vendored
Normal file
@@ -0,0 +1,873 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/menus/index.ts
|
||||
var index_exports = {};
|
||||
__export(index_exports, {
|
||||
BubbleMenu: () => BubbleMenu,
|
||||
FloatingMenu: () => FloatingMenu
|
||||
});
|
||||
module.exports = __toCommonJS(index_exports);
|
||||
|
||||
// ../extension-bubble-menu/src/bubble-menu-plugin.ts
|
||||
var import_dom = require("@floating-ui/dom");
|
||||
var import_core = require("@tiptap/core");
|
||||
var import_state = require("@tiptap/pm/state");
|
||||
var import_tables = require("@tiptap/pm/tables");
|
||||
function combineDOMRects(rect1, rect2) {
|
||||
const top = Math.min(rect1.top, rect2.top);
|
||||
const bottom = Math.max(rect1.bottom, rect2.bottom);
|
||||
const left = Math.min(rect1.left, rect2.left);
|
||||
const right = Math.max(rect1.right, rect2.right);
|
||||
const width = right - left;
|
||||
const height = bottom - top;
|
||||
const x = left;
|
||||
const y = top;
|
||||
return new DOMRect(x, y, width, height);
|
||||
}
|
||||
var BubbleMenuView = class {
|
||||
constructor({
|
||||
editor,
|
||||
element,
|
||||
view,
|
||||
pluginKey = "bubbleMenu",
|
||||
updateDelay = 250,
|
||||
resizeDelay = 60,
|
||||
shouldShow,
|
||||
appendTo,
|
||||
getReferencedVirtualElement,
|
||||
options
|
||||
}) {
|
||||
this.preventHide = false;
|
||||
this.isVisible = false;
|
||||
this.scrollTarget = window;
|
||||
this.floatingUIOptions = {
|
||||
strategy: "absolute",
|
||||
placement: "top",
|
||||
offset: 8,
|
||||
flip: {},
|
||||
shift: {},
|
||||
arrow: false,
|
||||
size: false,
|
||||
autoPlacement: false,
|
||||
hide: false,
|
||||
inline: false,
|
||||
onShow: void 0,
|
||||
onHide: void 0,
|
||||
onUpdate: void 0,
|
||||
onDestroy: void 0
|
||||
};
|
||||
this.shouldShow = ({ view, state, from, to }) => {
|
||||
const { doc, selection } = state;
|
||||
const { empty } = selection;
|
||||
const isEmptyTextBlock = !doc.textBetween(from, to).length && (0, import_core.isTextSelection)(state.selection);
|
||||
const isChildOfMenu = this.element.contains(document.activeElement);
|
||||
const hasEditorFocus = view.hasFocus() || isChildOfMenu;
|
||||
if (!hasEditorFocus || empty || isEmptyTextBlock || !this.editor.isEditable) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
this.mousedownHandler = () => {
|
||||
this.preventHide = true;
|
||||
};
|
||||
this.dragstartHandler = () => {
|
||||
this.hide();
|
||||
};
|
||||
/**
|
||||
* Handles the window resize event to update the position of the bubble menu.
|
||||
* It uses a debounce mechanism to prevent excessive updates.
|
||||
* The delay is defined by the `resizeDelay` property.
|
||||
*/
|
||||
this.resizeHandler = () => {
|
||||
if (this.resizeDebounceTimer) {
|
||||
clearTimeout(this.resizeDebounceTimer);
|
||||
}
|
||||
this.resizeDebounceTimer = window.setTimeout(() => {
|
||||
this.updatePosition();
|
||||
}, this.resizeDelay);
|
||||
};
|
||||
this.focusHandler = () => {
|
||||
setTimeout(() => this.update(this.editor.view));
|
||||
};
|
||||
this.blurHandler = ({ event }) => {
|
||||
var _a;
|
||||
if (this.editor.isDestroyed) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
if (this.preventHide) {
|
||||
this.preventHide = false;
|
||||
return;
|
||||
}
|
||||
if ((event == null ? void 0 : event.relatedTarget) && ((_a = this.element.parentNode) == null ? void 0 : _a.contains(event.relatedTarget))) {
|
||||
return;
|
||||
}
|
||||
if ((event == null ? void 0 : event.relatedTarget) === this.editor.view.dom) {
|
||||
return;
|
||||
}
|
||||
this.hide();
|
||||
};
|
||||
this.handleDebouncedUpdate = (view, oldState) => {
|
||||
const selectionChanged = !(oldState == null ? void 0 : oldState.selection.eq(view.state.selection));
|
||||
const docChanged = !(oldState == null ? void 0 : oldState.doc.eq(view.state.doc));
|
||||
if (!selectionChanged && !docChanged) {
|
||||
return;
|
||||
}
|
||||
if (this.updateDebounceTimer) {
|
||||
clearTimeout(this.updateDebounceTimer);
|
||||
}
|
||||
this.updateDebounceTimer = window.setTimeout(() => {
|
||||
this.updateHandler(view, selectionChanged, docChanged, oldState);
|
||||
}, this.updateDelay);
|
||||
};
|
||||
this.updateHandler = (view, selectionChanged, docChanged, oldState) => {
|
||||
const { composing } = view;
|
||||
const isSame = !selectionChanged && !docChanged;
|
||||
if (composing || isSame) {
|
||||
return;
|
||||
}
|
||||
const shouldShow = this.getShouldShow(oldState);
|
||||
if (!shouldShow) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
this.updatePosition();
|
||||
this.show();
|
||||
};
|
||||
/**
|
||||
* Handles the transaction event to update the position of the bubble menu.
|
||||
* This allows external code to trigger a position update via:
|
||||
* `editor.view.dispatch(editor.state.tr.setMeta(pluginKey, 'updatePosition'))`
|
||||
* The `pluginKey` defaults to `bubbleMenu`
|
||||
*/
|
||||
this.transactionHandler = ({ transaction: tr }) => {
|
||||
const meta = tr.getMeta(this.pluginKey);
|
||||
if (meta === "updatePosition") {
|
||||
this.updatePosition();
|
||||
} else if (meta && typeof meta === "object" && meta.type === "updateOptions") {
|
||||
this.updateOptions(meta.options);
|
||||
}
|
||||
};
|
||||
var _a;
|
||||
this.editor = editor;
|
||||
this.element = element;
|
||||
this.view = view;
|
||||
this.pluginKey = pluginKey;
|
||||
this.updateDelay = updateDelay;
|
||||
this.resizeDelay = resizeDelay;
|
||||
this.appendTo = appendTo;
|
||||
this.scrollTarget = (_a = options == null ? void 0 : options.scrollTarget) != null ? _a : window;
|
||||
this.getReferencedVirtualElement = getReferencedVirtualElement;
|
||||
this.floatingUIOptions = {
|
||||
...this.floatingUIOptions,
|
||||
...options
|
||||
};
|
||||
this.element.tabIndex = 0;
|
||||
if (shouldShow) {
|
||||
this.shouldShow = shouldShow;
|
||||
}
|
||||
this.element.addEventListener("mousedown", this.mousedownHandler, { capture: true });
|
||||
this.view.dom.addEventListener("dragstart", this.dragstartHandler);
|
||||
this.editor.on("focus", this.focusHandler);
|
||||
this.editor.on("blur", this.blurHandler);
|
||||
this.editor.on("transaction", this.transactionHandler);
|
||||
window.addEventListener("resize", this.resizeHandler);
|
||||
this.scrollTarget.addEventListener("scroll", this.resizeHandler);
|
||||
this.update(view, view.state);
|
||||
if (this.getShouldShow()) {
|
||||
this.show();
|
||||
this.updatePosition();
|
||||
}
|
||||
}
|
||||
get middlewares() {
|
||||
const middlewares = [];
|
||||
if (this.floatingUIOptions.flip) {
|
||||
middlewares.push((0, import_dom.flip)(typeof this.floatingUIOptions.flip !== "boolean" ? this.floatingUIOptions.flip : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.shift) {
|
||||
middlewares.push(
|
||||
(0, import_dom.shift)(typeof this.floatingUIOptions.shift !== "boolean" ? this.floatingUIOptions.shift : void 0)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.offset) {
|
||||
middlewares.push(
|
||||
(0, import_dom.offset)(typeof this.floatingUIOptions.offset !== "boolean" ? this.floatingUIOptions.offset : void 0)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.arrow) {
|
||||
middlewares.push((0, import_dom.arrow)(this.floatingUIOptions.arrow));
|
||||
}
|
||||
if (this.floatingUIOptions.size) {
|
||||
middlewares.push((0, import_dom.size)(typeof this.floatingUIOptions.size !== "boolean" ? this.floatingUIOptions.size : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.autoPlacement) {
|
||||
middlewares.push(
|
||||
(0, import_dom.autoPlacement)(
|
||||
typeof this.floatingUIOptions.autoPlacement !== "boolean" ? this.floatingUIOptions.autoPlacement : void 0
|
||||
)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.hide) {
|
||||
middlewares.push((0, import_dom.hide)(typeof this.floatingUIOptions.hide !== "boolean" ? this.floatingUIOptions.hide : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.inline) {
|
||||
middlewares.push(
|
||||
(0, import_dom.inline)(typeof this.floatingUIOptions.inline !== "boolean" ? this.floatingUIOptions.inline : void 0)
|
||||
);
|
||||
}
|
||||
return middlewares;
|
||||
}
|
||||
get virtualElement() {
|
||||
var _a, _b, _c;
|
||||
const { selection } = this.editor.state;
|
||||
const referencedVirtualElement = (_a = this.getReferencedVirtualElement) == null ? void 0 : _a.call(this);
|
||||
if (referencedVirtualElement) {
|
||||
return referencedVirtualElement;
|
||||
}
|
||||
if (!((_c = (_b = this.view) == null ? void 0 : _b.dom) == null ? void 0 : _c.parentNode)) {
|
||||
return;
|
||||
}
|
||||
const domRect = (0, import_core.posToDOMRect)(this.view, selection.from, selection.to);
|
||||
let virtualElement = {
|
||||
getBoundingClientRect: () => domRect,
|
||||
getClientRects: () => [domRect]
|
||||
};
|
||||
if (selection instanceof import_state.NodeSelection) {
|
||||
let node = this.view.nodeDOM(selection.from);
|
||||
const nodeViewWrapper = node.dataset.nodeViewWrapper ? node : node.querySelector("[data-node-view-wrapper]");
|
||||
if (nodeViewWrapper) {
|
||||
node = nodeViewWrapper;
|
||||
}
|
||||
if (node) {
|
||||
virtualElement = {
|
||||
getBoundingClientRect: () => node.getBoundingClientRect(),
|
||||
getClientRects: () => [node.getBoundingClientRect()]
|
||||
};
|
||||
}
|
||||
}
|
||||
if (selection instanceof import_tables.CellSelection) {
|
||||
const { $anchorCell, $headCell } = selection;
|
||||
const from = $anchorCell ? $anchorCell.pos : $headCell.pos;
|
||||
const to = $headCell ? $headCell.pos : $anchorCell.pos;
|
||||
const fromDOM = this.view.nodeDOM(from);
|
||||
const toDOM = this.view.nodeDOM(to);
|
||||
if (!fromDOM || !toDOM) {
|
||||
return;
|
||||
}
|
||||
const clientRect = fromDOM === toDOM ? fromDOM.getBoundingClientRect() : combineDOMRects(
|
||||
fromDOM.getBoundingClientRect(),
|
||||
toDOM.getBoundingClientRect()
|
||||
);
|
||||
virtualElement = {
|
||||
getBoundingClientRect: () => clientRect,
|
||||
getClientRects: () => [clientRect]
|
||||
};
|
||||
}
|
||||
return virtualElement;
|
||||
}
|
||||
updatePosition() {
|
||||
const virtualElement = this.virtualElement;
|
||||
if (!virtualElement) {
|
||||
return;
|
||||
}
|
||||
(0, import_dom.computePosition)(virtualElement, this.element, {
|
||||
placement: this.floatingUIOptions.placement,
|
||||
strategy: this.floatingUIOptions.strategy,
|
||||
middleware: this.middlewares
|
||||
}).then(({ x, y, strategy, middlewareData }) => {
|
||||
var _a, _b;
|
||||
if (((_a = middlewareData.hide) == null ? void 0 : _a.referenceHidden) || ((_b = middlewareData.hide) == null ? void 0 : _b.escaped)) {
|
||||
this.element.style.visibility = "hidden";
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "visible";
|
||||
this.element.style.width = "max-content";
|
||||
this.element.style.position = strategy;
|
||||
this.element.style.left = `${x}px`;
|
||||
this.element.style.top = `${y}px`;
|
||||
if (this.isVisible && this.floatingUIOptions.onUpdate) {
|
||||
this.floatingUIOptions.onUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
update(view, oldState) {
|
||||
const { state } = view;
|
||||
const hasValidSelection = state.selection.from !== state.selection.to;
|
||||
if (this.updateDelay > 0 && hasValidSelection) {
|
||||
this.handleDebouncedUpdate(view, oldState);
|
||||
return;
|
||||
}
|
||||
const selectionChanged = !(oldState == null ? void 0 : oldState.selection.eq(view.state.selection));
|
||||
const docChanged = !(oldState == null ? void 0 : oldState.doc.eq(view.state.doc));
|
||||
this.updateHandler(view, selectionChanged, docChanged, oldState);
|
||||
}
|
||||
getShouldShow(oldState) {
|
||||
var _a;
|
||||
const { state } = this.view;
|
||||
const { selection } = state;
|
||||
const { ranges } = selection;
|
||||
const from = Math.min(...ranges.map((range) => range.$from.pos));
|
||||
const to = Math.max(...ranges.map((range) => range.$to.pos));
|
||||
const shouldShow = (_a = this.shouldShow) == null ? void 0 : _a.call(this, {
|
||||
editor: this.editor,
|
||||
element: this.element,
|
||||
view: this.view,
|
||||
state,
|
||||
oldState,
|
||||
from,
|
||||
to
|
||||
});
|
||||
return shouldShow || false;
|
||||
}
|
||||
show() {
|
||||
var _a;
|
||||
if (this.isVisible) {
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "visible";
|
||||
this.element.style.opacity = "1";
|
||||
const appendToElement = typeof this.appendTo === "function" ? this.appendTo() : this.appendTo;
|
||||
(_a = appendToElement != null ? appendToElement : this.view.dom.parentElement) == null ? void 0 : _a.appendChild(this.element);
|
||||
if (this.floatingUIOptions.onShow) {
|
||||
this.floatingUIOptions.onShow();
|
||||
}
|
||||
this.isVisible = true;
|
||||
}
|
||||
hide() {
|
||||
if (!this.isVisible) {
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "hidden";
|
||||
this.element.style.opacity = "0";
|
||||
this.element.remove();
|
||||
if (this.floatingUIOptions.onHide) {
|
||||
this.floatingUIOptions.onHide();
|
||||
}
|
||||
this.isVisible = false;
|
||||
}
|
||||
updateOptions(newProps) {
|
||||
var _a;
|
||||
if (newProps.updateDelay !== void 0) {
|
||||
this.updateDelay = newProps.updateDelay;
|
||||
}
|
||||
if (newProps.resizeDelay !== void 0) {
|
||||
this.resizeDelay = newProps.resizeDelay;
|
||||
}
|
||||
if (newProps.appendTo !== void 0) {
|
||||
this.appendTo = newProps.appendTo;
|
||||
}
|
||||
if (newProps.getReferencedVirtualElement !== void 0) {
|
||||
this.getReferencedVirtualElement = newProps.getReferencedVirtualElement;
|
||||
}
|
||||
if (newProps.shouldShow !== void 0) {
|
||||
if (newProps.shouldShow) {
|
||||
this.shouldShow = newProps.shouldShow;
|
||||
}
|
||||
}
|
||||
if (newProps.options !== void 0) {
|
||||
const newScrollTarget = (_a = newProps.options.scrollTarget) != null ? _a : window;
|
||||
if (newScrollTarget !== this.scrollTarget) {
|
||||
this.scrollTarget.removeEventListener("scroll", this.resizeHandler);
|
||||
this.scrollTarget = newScrollTarget;
|
||||
this.scrollTarget.addEventListener("scroll", this.resizeHandler);
|
||||
}
|
||||
this.floatingUIOptions = {
|
||||
...this.floatingUIOptions,
|
||||
...newProps.options
|
||||
};
|
||||
}
|
||||
}
|
||||
destroy() {
|
||||
this.hide();
|
||||
this.element.removeEventListener("mousedown", this.mousedownHandler, { capture: true });
|
||||
this.view.dom.removeEventListener("dragstart", this.dragstartHandler);
|
||||
window.removeEventListener("resize", this.resizeHandler);
|
||||
this.scrollTarget.removeEventListener("scroll", this.resizeHandler);
|
||||
this.editor.off("focus", this.focusHandler);
|
||||
this.editor.off("blur", this.blurHandler);
|
||||
this.editor.off("transaction", this.transactionHandler);
|
||||
if (this.floatingUIOptions.onDestroy) {
|
||||
this.floatingUIOptions.onDestroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
var BubbleMenuPlugin = (options) => {
|
||||
return new import_state.Plugin({
|
||||
key: typeof options.pluginKey === "string" ? new import_state.PluginKey(options.pluginKey) : options.pluginKey,
|
||||
view: (view) => new BubbleMenuView({ view, ...options })
|
||||
});
|
||||
};
|
||||
|
||||
// src/menus/BubbleMenu.ts
|
||||
var import_state2 = require("@tiptap/pm/state");
|
||||
var import_vue = require("vue");
|
||||
var BubbleMenu = (0, import_vue.defineComponent)({
|
||||
name: "BubbleMenu",
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
pluginKey: {
|
||||
type: [String, Object],
|
||||
default: void 0
|
||||
},
|
||||
editor: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
updateDelay: {
|
||||
type: Number,
|
||||
default: void 0
|
||||
},
|
||||
resizeDelay: {
|
||||
type: Number,
|
||||
default: void 0
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
appendTo: {
|
||||
type: [Object, Function],
|
||||
default: void 0
|
||||
},
|
||||
shouldShow: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
getReferencedVirtualElement: {
|
||||
type: Function,
|
||||
default: void 0
|
||||
}
|
||||
},
|
||||
setup(props, { slots, attrs }) {
|
||||
var _a;
|
||||
const root = (0, import_vue.ref)(null);
|
||||
const resolvedPluginKey = (_a = props.pluginKey) != null ? _a : new import_state2.PluginKey("bubbleMenu");
|
||||
(0, import_vue.onMounted)(() => {
|
||||
const { editor, options, resizeDelay, appendTo, shouldShow, getReferencedVirtualElement, updateDelay } = props;
|
||||
const el = root.value;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
el.style.visibility = "hidden";
|
||||
el.style.position = "absolute";
|
||||
el.remove();
|
||||
(0, import_vue.nextTick)(() => {
|
||||
editor.registerPlugin(
|
||||
BubbleMenuPlugin({
|
||||
editor,
|
||||
element: el,
|
||||
options,
|
||||
pluginKey: resolvedPluginKey,
|
||||
resizeDelay,
|
||||
appendTo,
|
||||
shouldShow,
|
||||
getReferencedVirtualElement,
|
||||
updateDelay
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
(0, import_vue.onBeforeUnmount)(() => {
|
||||
const { editor } = props;
|
||||
editor.unregisterPlugin(resolvedPluginKey);
|
||||
});
|
||||
return () => {
|
||||
var _a2;
|
||||
return (0, import_vue.h)("div", { ref: root, ...attrs }, (_a2 = slots.default) == null ? void 0 : _a2.call(slots));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// ../extension-floating-menu/src/floating-menu-plugin.ts
|
||||
var import_dom2 = require("@floating-ui/dom");
|
||||
var import_core2 = require("@tiptap/core");
|
||||
var import_state3 = require("@tiptap/pm/state");
|
||||
var FloatingMenuView = class {
|
||||
constructor({
|
||||
editor,
|
||||
element,
|
||||
view,
|
||||
pluginKey = "floatingMenu",
|
||||
updateDelay = 250,
|
||||
resizeDelay = 60,
|
||||
options,
|
||||
appendTo,
|
||||
shouldShow
|
||||
}) {
|
||||
this.preventHide = false;
|
||||
this.isVisible = false;
|
||||
this.scrollTarget = window;
|
||||
this.shouldShow = ({ view, state }) => {
|
||||
const { selection } = state;
|
||||
const { $anchor, empty } = selection;
|
||||
const isRootDepth = $anchor.depth === 1;
|
||||
const isEmptyTextBlock = $anchor.parent.isTextblock && !$anchor.parent.type.spec.code && !$anchor.parent.textContent && $anchor.parent.childCount === 0 && !this.getTextContent($anchor.parent);
|
||||
if (!view.hasFocus() || !empty || !isRootDepth || !isEmptyTextBlock || !this.editor.isEditable) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
this.floatingUIOptions = {
|
||||
strategy: "absolute",
|
||||
placement: "right",
|
||||
offset: 8,
|
||||
flip: {},
|
||||
shift: {},
|
||||
arrow: false,
|
||||
size: false,
|
||||
autoPlacement: false,
|
||||
hide: false,
|
||||
inline: false
|
||||
};
|
||||
this.updateHandler = (view, selectionChanged, docChanged, oldState) => {
|
||||
const { composing } = view;
|
||||
const isSame = !selectionChanged && !docChanged;
|
||||
if (composing || isSame) {
|
||||
return;
|
||||
}
|
||||
const shouldShow = this.getShouldShow(oldState);
|
||||
if (!shouldShow) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
this.updatePosition();
|
||||
this.show();
|
||||
};
|
||||
this.mousedownHandler = () => {
|
||||
this.preventHide = true;
|
||||
};
|
||||
this.focusHandler = () => {
|
||||
setTimeout(() => this.update(this.editor.view));
|
||||
};
|
||||
this.blurHandler = ({ event }) => {
|
||||
var _a;
|
||||
if (this.preventHide) {
|
||||
this.preventHide = false;
|
||||
return;
|
||||
}
|
||||
if ((event == null ? void 0 : event.relatedTarget) && ((_a = this.element.parentNode) == null ? void 0 : _a.contains(event.relatedTarget))) {
|
||||
return;
|
||||
}
|
||||
if ((event == null ? void 0 : event.relatedTarget) === this.editor.view.dom) {
|
||||
return;
|
||||
}
|
||||
this.hide();
|
||||
};
|
||||
/**
|
||||
* Handles the transaction event to update the position of the floating menu.
|
||||
* This allows external code to trigger a position update via:
|
||||
* `editor.view.dispatch(editor.state.tr.setMeta(pluginKey, 'updatePosition'))`
|
||||
* The `pluginKey` defaults to `floatingMenu`
|
||||
*/
|
||||
this.transactionHandler = ({ transaction: tr }) => {
|
||||
const meta = tr.getMeta(this.pluginKey);
|
||||
if (meta === "updatePosition") {
|
||||
this.updatePosition();
|
||||
} else if (meta && typeof meta === "object" && meta.type === "updateOptions") {
|
||||
this.updateOptions(meta.options);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Handles the window resize event to update the position of the floating menu.
|
||||
* It uses a debounce mechanism to prevent excessive updates.
|
||||
* The delay is defined by the `resizeDelay` property.
|
||||
*/
|
||||
this.resizeHandler = () => {
|
||||
if (this.resizeDebounceTimer) {
|
||||
clearTimeout(this.resizeDebounceTimer);
|
||||
}
|
||||
this.resizeDebounceTimer = window.setTimeout(() => {
|
||||
this.updatePosition();
|
||||
}, this.resizeDelay);
|
||||
};
|
||||
var _a;
|
||||
this.editor = editor;
|
||||
this.element = element;
|
||||
this.view = view;
|
||||
this.pluginKey = pluginKey;
|
||||
this.updateDelay = updateDelay;
|
||||
this.resizeDelay = resizeDelay;
|
||||
this.appendTo = appendTo;
|
||||
this.scrollTarget = (_a = options == null ? void 0 : options.scrollTarget) != null ? _a : window;
|
||||
this.floatingUIOptions = {
|
||||
...this.floatingUIOptions,
|
||||
...options
|
||||
};
|
||||
this.element.tabIndex = 0;
|
||||
if (shouldShow) {
|
||||
this.shouldShow = shouldShow;
|
||||
}
|
||||
this.element.addEventListener("mousedown", this.mousedownHandler, { capture: true });
|
||||
this.editor.on("focus", this.focusHandler);
|
||||
this.editor.on("blur", this.blurHandler);
|
||||
this.editor.on("transaction", this.transactionHandler);
|
||||
window.addEventListener("resize", this.resizeHandler);
|
||||
this.scrollTarget.addEventListener("scroll", this.resizeHandler);
|
||||
this.update(view, view.state);
|
||||
if (this.getShouldShow()) {
|
||||
this.show();
|
||||
this.updatePosition();
|
||||
}
|
||||
}
|
||||
getTextContent(node) {
|
||||
return (0, import_core2.getText)(node, { textSerializers: (0, import_core2.getTextSerializersFromSchema)(this.editor.schema) });
|
||||
}
|
||||
get middlewares() {
|
||||
const middlewares = [];
|
||||
if (this.floatingUIOptions.flip) {
|
||||
middlewares.push((0, import_dom2.flip)(typeof this.floatingUIOptions.flip !== "boolean" ? this.floatingUIOptions.flip : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.shift) {
|
||||
middlewares.push(
|
||||
(0, import_dom2.shift)(typeof this.floatingUIOptions.shift !== "boolean" ? this.floatingUIOptions.shift : void 0)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.offset) {
|
||||
middlewares.push(
|
||||
(0, import_dom2.offset)(typeof this.floatingUIOptions.offset !== "boolean" ? this.floatingUIOptions.offset : void 0)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.arrow) {
|
||||
middlewares.push((0, import_dom2.arrow)(this.floatingUIOptions.arrow));
|
||||
}
|
||||
if (this.floatingUIOptions.size) {
|
||||
middlewares.push((0, import_dom2.size)(typeof this.floatingUIOptions.size !== "boolean" ? this.floatingUIOptions.size : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.autoPlacement) {
|
||||
middlewares.push(
|
||||
(0, import_dom2.autoPlacement)(
|
||||
typeof this.floatingUIOptions.autoPlacement !== "boolean" ? this.floatingUIOptions.autoPlacement : void 0
|
||||
)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.hide) {
|
||||
middlewares.push((0, import_dom2.hide)(typeof this.floatingUIOptions.hide !== "boolean" ? this.floatingUIOptions.hide : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.inline) {
|
||||
middlewares.push(
|
||||
(0, import_dom2.inline)(typeof this.floatingUIOptions.inline !== "boolean" ? this.floatingUIOptions.inline : void 0)
|
||||
);
|
||||
}
|
||||
return middlewares;
|
||||
}
|
||||
getShouldShow(oldState) {
|
||||
var _a;
|
||||
const { state } = this.view;
|
||||
const { selection } = state;
|
||||
const { ranges } = selection;
|
||||
const from = Math.min(...ranges.map((range) => range.$from.pos));
|
||||
const to = Math.max(...ranges.map((range) => range.$to.pos));
|
||||
const shouldShow = (_a = this.shouldShow) == null ? void 0 : _a.call(this, {
|
||||
editor: this.editor,
|
||||
view: this.view,
|
||||
state,
|
||||
oldState,
|
||||
from,
|
||||
to
|
||||
});
|
||||
return shouldShow;
|
||||
}
|
||||
updateOptions(newProps) {
|
||||
var _a;
|
||||
if (newProps.updateDelay !== void 0) {
|
||||
this.updateDelay = newProps.updateDelay;
|
||||
}
|
||||
if (newProps.resizeDelay !== void 0) {
|
||||
this.resizeDelay = newProps.resizeDelay;
|
||||
}
|
||||
if (newProps.appendTo !== void 0) {
|
||||
this.appendTo = newProps.appendTo;
|
||||
}
|
||||
if (newProps.shouldShow !== void 0) {
|
||||
if (newProps.shouldShow) {
|
||||
this.shouldShow = newProps.shouldShow;
|
||||
}
|
||||
}
|
||||
if (newProps.options !== void 0) {
|
||||
const newScrollTarget = (_a = newProps.options.scrollTarget) != null ? _a : window;
|
||||
if (newScrollTarget !== this.scrollTarget) {
|
||||
this.scrollTarget.removeEventListener("scroll", this.resizeHandler);
|
||||
this.scrollTarget = newScrollTarget;
|
||||
this.scrollTarget.addEventListener("scroll", this.resizeHandler);
|
||||
}
|
||||
this.floatingUIOptions = {
|
||||
...this.floatingUIOptions,
|
||||
...newProps.options
|
||||
};
|
||||
}
|
||||
}
|
||||
updatePosition() {
|
||||
const { selection } = this.editor.state;
|
||||
const domRect = (0, import_core2.posToDOMRect)(this.view, selection.from, selection.to);
|
||||
const virtualElement = {
|
||||
getBoundingClientRect: () => domRect,
|
||||
getClientRects: () => [domRect]
|
||||
};
|
||||
(0, import_dom2.computePosition)(virtualElement, this.element, {
|
||||
placement: this.floatingUIOptions.placement,
|
||||
strategy: this.floatingUIOptions.strategy,
|
||||
middleware: this.middlewares
|
||||
}).then(({ x, y, strategy, middlewareData }) => {
|
||||
var _a, _b;
|
||||
if (((_a = middlewareData.hide) == null ? void 0 : _a.referenceHidden) || ((_b = middlewareData.hide) == null ? void 0 : _b.escaped)) {
|
||||
this.element.style.visibility = "hidden";
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "visible";
|
||||
this.element.style.width = "max-content";
|
||||
this.element.style.position = strategy;
|
||||
this.element.style.left = `${x}px`;
|
||||
this.element.style.top = `${y}px`;
|
||||
if (this.isVisible && this.floatingUIOptions.onUpdate) {
|
||||
this.floatingUIOptions.onUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
update(view, oldState) {
|
||||
const selectionChanged = !(oldState == null ? void 0 : oldState.selection.eq(view.state.selection));
|
||||
const docChanged = !(oldState == null ? void 0 : oldState.doc.eq(view.state.doc));
|
||||
this.updateHandler(view, selectionChanged, docChanged, oldState);
|
||||
}
|
||||
show() {
|
||||
var _a;
|
||||
if (this.isVisible) {
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "visible";
|
||||
this.element.style.opacity = "1";
|
||||
const appendToElement = typeof this.appendTo === "function" ? this.appendTo() : this.appendTo;
|
||||
(_a = appendToElement != null ? appendToElement : this.view.dom.parentElement) == null ? void 0 : _a.appendChild(this.element);
|
||||
if (this.floatingUIOptions.onShow) {
|
||||
this.floatingUIOptions.onShow();
|
||||
}
|
||||
this.isVisible = true;
|
||||
}
|
||||
hide() {
|
||||
if (!this.isVisible) {
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "hidden";
|
||||
this.element.style.opacity = "0";
|
||||
this.element.remove();
|
||||
if (this.floatingUIOptions.onHide) {
|
||||
this.floatingUIOptions.onHide();
|
||||
}
|
||||
this.isVisible = false;
|
||||
}
|
||||
destroy() {
|
||||
this.hide();
|
||||
this.element.removeEventListener("mousedown", this.mousedownHandler, { capture: true });
|
||||
window.removeEventListener("resize", this.resizeHandler);
|
||||
this.scrollTarget.removeEventListener("scroll", this.resizeHandler);
|
||||
this.editor.off("focus", this.focusHandler);
|
||||
this.editor.off("blur", this.blurHandler);
|
||||
this.editor.off("transaction", this.transactionHandler);
|
||||
if (this.floatingUIOptions.onDestroy) {
|
||||
this.floatingUIOptions.onDestroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
var FloatingMenuPlugin = (options) => {
|
||||
return new import_state3.Plugin({
|
||||
key: typeof options.pluginKey === "string" ? new import_state3.PluginKey(options.pluginKey) : options.pluginKey,
|
||||
view: (view) => new FloatingMenuView({ view, ...options })
|
||||
});
|
||||
};
|
||||
|
||||
// src/menus/FloatingMenu.ts
|
||||
var import_state4 = require("@tiptap/pm/state");
|
||||
var import_vue2 = require("vue");
|
||||
var FloatingMenu = (0, import_vue2.defineComponent)({
|
||||
name: "FloatingMenu",
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
pluginKey: {
|
||||
// TODO: TypeScript breaks :(
|
||||
// type: [String, Object as PropType<Exclude<FloatingMenuPluginProps['pluginKey'], string>>],
|
||||
type: null,
|
||||
default: void 0
|
||||
},
|
||||
editor: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
updateDelay: {
|
||||
type: Number,
|
||||
default: void 0
|
||||
},
|
||||
resizeDelay: {
|
||||
type: Number,
|
||||
default: void 0
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
appendTo: {
|
||||
type: [Object, Function],
|
||||
default: void 0
|
||||
},
|
||||
shouldShow: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
setup(props, { slots, attrs }) {
|
||||
var _a;
|
||||
const root = (0, import_vue2.ref)(null);
|
||||
const resolvedPluginKey = (_a = props.pluginKey) != null ? _a : new import_state4.PluginKey("floatingMenu");
|
||||
(0, import_vue2.onMounted)(() => {
|
||||
const { editor, updateDelay, resizeDelay, options, appendTo, shouldShow } = props;
|
||||
const el = root.value;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
el.style.visibility = "hidden";
|
||||
el.style.position = "absolute";
|
||||
el.remove();
|
||||
editor.registerPlugin(
|
||||
FloatingMenuPlugin({
|
||||
pluginKey: resolvedPluginKey,
|
||||
editor,
|
||||
element: el,
|
||||
updateDelay,
|
||||
resizeDelay,
|
||||
options,
|
||||
appendTo,
|
||||
shouldShow
|
||||
})
|
||||
);
|
||||
});
|
||||
(0, import_vue2.onBeforeUnmount)(() => {
|
||||
const { editor } = props;
|
||||
editor.unregisterPlugin(resolvedPluginKey);
|
||||
});
|
||||
return () => {
|
||||
var _a2;
|
||||
return (0, import_vue2.h)("div", { ref: root, ...attrs }, (_a2 = slots.default) == null ? void 0 : _a2.call(slots));
|
||||
};
|
||||
}
|
||||
});
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
BubbleMenu,
|
||||
FloatingMenu
|
||||
});
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
1
node_modules/@tiptap/vue-3/dist/menus/index.cjs.map
generated
vendored
Normal file
1
node_modules/@tiptap/vue-3/dist/menus/index.cjs.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
389
node_modules/@tiptap/vue-3/dist/menus/index.d.cts
generated
vendored
Normal file
389
node_modules/@tiptap/vue-3/dist/menus/index.d.cts
generated
vendored
Normal file
@@ -0,0 +1,389 @@
|
||||
import * as prosemirror_state from 'prosemirror-state';
|
||||
import * as prosemirror_view from 'prosemirror-view';
|
||||
import * as _tiptap_core from '@tiptap/core';
|
||||
import { Editor } from '@tiptap/core';
|
||||
import * as _floating_ui_dom from '@floating-ui/dom';
|
||||
import { VirtualElement, offset, flip, shift, arrow, size, autoPlacement, hide, inline } from '@floating-ui/dom';
|
||||
import * as vue from 'vue';
|
||||
import { PropType } from 'vue';
|
||||
import { PluginKey, EditorState } from '@tiptap/pm/state';
|
||||
import { EditorView } from '@tiptap/pm/view';
|
||||
|
||||
interface BubbleMenuPluginProps {
|
||||
/**
|
||||
* The plugin key.
|
||||
* @type {PluginKey | string}
|
||||
* @default 'bubbleMenu'
|
||||
*/
|
||||
pluginKey: PluginKey | string;
|
||||
/**
|
||||
* The editor instance.
|
||||
*/
|
||||
editor: Editor;
|
||||
/**
|
||||
* The DOM element that contains your menu.
|
||||
* @type {HTMLElement}
|
||||
* @default null
|
||||
*/
|
||||
element: HTMLElement;
|
||||
/**
|
||||
* The delay in milliseconds before the menu should be updated.
|
||||
* This can be useful to prevent performance issues.
|
||||
* @type {number}
|
||||
* @default 250
|
||||
*/
|
||||
updateDelay?: number;
|
||||
/**
|
||||
* The delay in milliseconds before the menu position should be updated on window resize.
|
||||
* This can be useful to prevent performance issues.
|
||||
* @type {number}
|
||||
* @default 60
|
||||
*/
|
||||
resizeDelay?: number;
|
||||
/**
|
||||
* A function that determines whether the menu should be shown or not.
|
||||
* If this function returns `false`, the menu will be hidden, otherwise it will be shown.
|
||||
*/
|
||||
shouldShow?: ((props: {
|
||||
editor: Editor;
|
||||
element: HTMLElement;
|
||||
view: EditorView;
|
||||
state: EditorState;
|
||||
oldState?: EditorState;
|
||||
from: number;
|
||||
to: number;
|
||||
}) => boolean) | null;
|
||||
/**
|
||||
* The DOM element to append your menu to. Default is the editor's parent element.
|
||||
*
|
||||
* Sometimes the menu needs to be appended to a different DOM context due to accessibility, clipping, or z-index issues.
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
* @default null
|
||||
*/
|
||||
appendTo?: HTMLElement | (() => HTMLElement);
|
||||
/**
|
||||
* A function that returns the virtual element for the menu.
|
||||
* This is useful when the menu needs to be positioned relative to a specific DOM element.
|
||||
* @type {() => VirtualElement | null}
|
||||
* @default Position based on the selection.
|
||||
*/
|
||||
getReferencedVirtualElement?: () => VirtualElement | null;
|
||||
/**
|
||||
* The options for the bubble menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, arrow, size, autoPlacement,
|
||||
* hide, and inline middlewares.
|
||||
* @default {}
|
||||
* @see https://floating-ui.com/docs/computePosition#options
|
||||
*/
|
||||
options?: {
|
||||
strategy?: 'absolute' | 'fixed';
|
||||
placement?: 'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end';
|
||||
offset?: Parameters<typeof offset>[0] | boolean;
|
||||
flip?: Parameters<typeof flip>[0] | boolean;
|
||||
shift?: Parameters<typeof shift>[0] | boolean;
|
||||
arrow?: Parameters<typeof arrow>[0] | false;
|
||||
size?: Parameters<typeof size>[0] | boolean;
|
||||
autoPlacement?: Parameters<typeof autoPlacement>[0] | boolean;
|
||||
hide?: Parameters<typeof hide>[0] | boolean;
|
||||
inline?: Parameters<typeof inline>[0] | boolean;
|
||||
onShow?: () => void;
|
||||
onHide?: () => void;
|
||||
onUpdate?: () => void;
|
||||
onDestroy?: () => void;
|
||||
/**
|
||||
* The scrollable element that should be listened to when updating the position of the bubble menu.
|
||||
* If not provided, the window will be used.
|
||||
* @type {HTMLElement | Window}
|
||||
*/
|
||||
scrollTarget?: HTMLElement | Window;
|
||||
};
|
||||
}
|
||||
|
||||
declare const BubbleMenu: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
pluginKey: {
|
||||
type: PropType<BubbleMenuPluginProps["pluginKey"]>;
|
||||
default: undefined;
|
||||
};
|
||||
editor: {
|
||||
type: PropType<BubbleMenuPluginProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
updateDelay: {
|
||||
type: PropType<BubbleMenuPluginProps["updateDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
resizeDelay: {
|
||||
type: PropType<BubbleMenuPluginProps["resizeDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
options: {
|
||||
type: PropType<BubbleMenuPluginProps["options"]>;
|
||||
default: () => {};
|
||||
};
|
||||
appendTo: {
|
||||
type: PropType<BubbleMenuPluginProps["appendTo"]>;
|
||||
default: undefined;
|
||||
};
|
||||
shouldShow: {
|
||||
type: PropType<Exclude<Required<BubbleMenuPluginProps>["shouldShow"], null>>;
|
||||
default: null;
|
||||
};
|
||||
getReferencedVirtualElement: {
|
||||
type: PropType<Exclude<Required<BubbleMenuPluginProps>["getReferencedVirtualElement"], null>>;
|
||||
default: undefined;
|
||||
};
|
||||
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
||||
[key: string]: any;
|
||||
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
pluginKey: {
|
||||
type: PropType<BubbleMenuPluginProps["pluginKey"]>;
|
||||
default: undefined;
|
||||
};
|
||||
editor: {
|
||||
type: PropType<BubbleMenuPluginProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
updateDelay: {
|
||||
type: PropType<BubbleMenuPluginProps["updateDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
resizeDelay: {
|
||||
type: PropType<BubbleMenuPluginProps["resizeDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
options: {
|
||||
type: PropType<BubbleMenuPluginProps["options"]>;
|
||||
default: () => {};
|
||||
};
|
||||
appendTo: {
|
||||
type: PropType<BubbleMenuPluginProps["appendTo"]>;
|
||||
default: undefined;
|
||||
};
|
||||
shouldShow: {
|
||||
type: PropType<Exclude<Required<BubbleMenuPluginProps>["shouldShow"], null>>;
|
||||
default: null;
|
||||
};
|
||||
getReferencedVirtualElement: {
|
||||
type: PropType<Exclude<Required<BubbleMenuPluginProps>["getReferencedVirtualElement"], null>>;
|
||||
default: undefined;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
options: {
|
||||
strategy?: "absolute" | "fixed";
|
||||
placement?: "top" | "right" | "bottom" | "left" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end";
|
||||
offset?: Parameters<typeof _floating_ui_dom.offset>[0] | boolean;
|
||||
flip?: Parameters<typeof _floating_ui_dom.flip>[0] | boolean;
|
||||
shift?: Parameters<typeof _floating_ui_dom.shift>[0] | boolean;
|
||||
arrow?: Parameters<typeof _floating_ui_dom.arrow>[0] | false;
|
||||
size?: Parameters<typeof _floating_ui_dom.size>[0] | boolean;
|
||||
autoPlacement?: Parameters<typeof _floating_ui_dom.autoPlacement>[0] | boolean;
|
||||
hide?: Parameters<typeof _floating_ui_dom.hide>[0] | boolean;
|
||||
inline?: Parameters<typeof _floating_ui_dom.inline>[0] | boolean;
|
||||
onShow?: () => void;
|
||||
onHide?: () => void;
|
||||
onUpdate?: () => void;
|
||||
onDestroy?: () => void;
|
||||
scrollTarget?: HTMLElement | Window;
|
||||
} | undefined;
|
||||
pluginKey: string | PluginKey<any>;
|
||||
updateDelay: number | undefined;
|
||||
resizeDelay: number | undefined;
|
||||
appendTo: HTMLElement | (() => HTMLElement) | undefined;
|
||||
getReferencedVirtualElement: () => _floating_ui_dom.VirtualElement | null;
|
||||
shouldShow: (props: {
|
||||
editor: _tiptap_core.Editor;
|
||||
element: HTMLElement;
|
||||
view: prosemirror_view.EditorView;
|
||||
state: prosemirror_state.EditorState;
|
||||
oldState?: prosemirror_state.EditorState;
|
||||
from: number;
|
||||
to: number;
|
||||
}) => boolean;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
|
||||
interface FloatingMenuPluginProps {
|
||||
/**
|
||||
* The plugin key for the floating menu.
|
||||
* @default 'floatingMenu'
|
||||
*/
|
||||
pluginKey: PluginKey | string;
|
||||
/**
|
||||
* The editor instance.
|
||||
* @default null
|
||||
*/
|
||||
editor: Editor;
|
||||
/**
|
||||
* The DOM element that contains your menu.
|
||||
* @default null
|
||||
*/
|
||||
element: HTMLElement;
|
||||
/**
|
||||
* The delay in milliseconds before the menu should be updated.
|
||||
* This can be useful to prevent performance issues.
|
||||
* @type {number}
|
||||
* @default 250
|
||||
*/
|
||||
updateDelay?: number;
|
||||
/**
|
||||
* The delay in milliseconds before the menu position should be updated on window resize.
|
||||
* This can be useful to prevent performance issues.
|
||||
* @type {number}
|
||||
* @default 60
|
||||
*/
|
||||
resizeDelay?: number;
|
||||
/**
|
||||
* The DOM element to append your menu to. Default is the editor's parent element.
|
||||
*
|
||||
* Sometimes the menu needs to be appended to a different DOM context due to accessibility, clipping, or z-index issues.
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
* @default null
|
||||
*/
|
||||
appendTo?: HTMLElement | (() => HTMLElement);
|
||||
/**
|
||||
* A function that determines whether the menu should be shown or not.
|
||||
* If this function returns `false`, the menu will be hidden, otherwise it will be shown.
|
||||
*/
|
||||
shouldShow?: ((props: {
|
||||
editor: Editor;
|
||||
view: EditorView;
|
||||
state: EditorState;
|
||||
oldState?: EditorState;
|
||||
from: number;
|
||||
to: number;
|
||||
}) => boolean) | null;
|
||||
/**
|
||||
* The options for the floating menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, arrow, size, autoPlacement,
|
||||
* hide, and inline middlewares.
|
||||
* @default {}
|
||||
* @see https://floating-ui.com/docs/computePosition#options
|
||||
*/
|
||||
options?: {
|
||||
strategy?: 'absolute' | 'fixed';
|
||||
placement?: 'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end';
|
||||
offset?: Parameters<typeof offset>[0] | boolean;
|
||||
flip?: Parameters<typeof flip>[0] | boolean;
|
||||
shift?: Parameters<typeof shift>[0] | boolean;
|
||||
arrow?: Parameters<typeof arrow>[0] | false;
|
||||
size?: Parameters<typeof size>[0] | boolean;
|
||||
autoPlacement?: Parameters<typeof autoPlacement>[0] | boolean;
|
||||
hide?: Parameters<typeof hide>[0] | boolean;
|
||||
inline?: Parameters<typeof inline>[0] | boolean;
|
||||
onShow?: () => void;
|
||||
onHide?: () => void;
|
||||
onUpdate?: () => void;
|
||||
onDestroy?: () => void;
|
||||
/**
|
||||
* The scrollable element that should be listened to when updating the position of the floating menu.
|
||||
* If not provided, the window will be used.
|
||||
* @type {HTMLElement | Window}
|
||||
*/
|
||||
scrollTarget?: HTMLElement | Window;
|
||||
};
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
floatingMenu: {
|
||||
/**
|
||||
* Update the position of the floating menu.
|
||||
* @example editor.commands.updateFloatingMenuPosition()
|
||||
*/
|
||||
updateFloatingMenuPosition: () => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare const FloatingMenu: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
pluginKey: {
|
||||
type: null;
|
||||
default: undefined;
|
||||
};
|
||||
editor: {
|
||||
type: PropType<FloatingMenuPluginProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
updateDelay: {
|
||||
type: PropType<FloatingMenuPluginProps["updateDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
resizeDelay: {
|
||||
type: PropType<FloatingMenuPluginProps["resizeDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
options: {
|
||||
type: PropType<FloatingMenuPluginProps["options"]>;
|
||||
default: () => {};
|
||||
};
|
||||
appendTo: {
|
||||
type: PropType<FloatingMenuPluginProps["appendTo"]>;
|
||||
default: undefined;
|
||||
};
|
||||
shouldShow: {
|
||||
type: PropType<Exclude<Required<FloatingMenuPluginProps>["shouldShow"], null>>;
|
||||
default: null;
|
||||
};
|
||||
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
||||
[key: string]: any;
|
||||
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
pluginKey: {
|
||||
type: null;
|
||||
default: undefined;
|
||||
};
|
||||
editor: {
|
||||
type: PropType<FloatingMenuPluginProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
updateDelay: {
|
||||
type: PropType<FloatingMenuPluginProps["updateDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
resizeDelay: {
|
||||
type: PropType<FloatingMenuPluginProps["resizeDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
options: {
|
||||
type: PropType<FloatingMenuPluginProps["options"]>;
|
||||
default: () => {};
|
||||
};
|
||||
appendTo: {
|
||||
type: PropType<FloatingMenuPluginProps["appendTo"]>;
|
||||
default: undefined;
|
||||
};
|
||||
shouldShow: {
|
||||
type: PropType<Exclude<Required<FloatingMenuPluginProps>["shouldShow"], null>>;
|
||||
default: null;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
options: {
|
||||
strategy?: "absolute" | "fixed";
|
||||
placement?: "top" | "right" | "bottom" | "left" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end";
|
||||
offset?: Parameters<typeof _floating_ui_dom.offset>[0] | boolean;
|
||||
flip?: Parameters<typeof _floating_ui_dom.flip>[0] | boolean;
|
||||
shift?: Parameters<typeof _floating_ui_dom.shift>[0] | boolean;
|
||||
arrow?: Parameters<typeof _floating_ui_dom.arrow>[0] | false;
|
||||
size?: Parameters<typeof _floating_ui_dom.size>[0] | boolean;
|
||||
autoPlacement?: Parameters<typeof _floating_ui_dom.autoPlacement>[0] | boolean;
|
||||
hide?: Parameters<typeof _floating_ui_dom.hide>[0] | boolean;
|
||||
inline?: Parameters<typeof _floating_ui_dom.inline>[0] | boolean;
|
||||
onShow?: () => void;
|
||||
onHide?: () => void;
|
||||
onUpdate?: () => void;
|
||||
onDestroy?: () => void;
|
||||
scrollTarget?: HTMLElement | Window;
|
||||
} | undefined;
|
||||
pluginKey: any;
|
||||
updateDelay: number | undefined;
|
||||
resizeDelay: number | undefined;
|
||||
appendTo: HTMLElement | (() => HTMLElement) | undefined;
|
||||
shouldShow: (props: {
|
||||
editor: _tiptap_core.Editor;
|
||||
view: prosemirror_view.EditorView;
|
||||
state: prosemirror_state.EditorState;
|
||||
oldState?: prosemirror_state.EditorState;
|
||||
from: number;
|
||||
to: number;
|
||||
}) => boolean;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
|
||||
export { BubbleMenu, FloatingMenu };
|
||||
389
node_modules/@tiptap/vue-3/dist/menus/index.d.ts
generated
vendored
Normal file
389
node_modules/@tiptap/vue-3/dist/menus/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,389 @@
|
||||
import * as prosemirror_state from 'prosemirror-state';
|
||||
import * as prosemirror_view from 'prosemirror-view';
|
||||
import * as _tiptap_core from '@tiptap/core';
|
||||
import { Editor } from '@tiptap/core';
|
||||
import * as _floating_ui_dom from '@floating-ui/dom';
|
||||
import { VirtualElement, offset, flip, shift, arrow, size, autoPlacement, hide, inline } from '@floating-ui/dom';
|
||||
import * as vue from 'vue';
|
||||
import { PropType } from 'vue';
|
||||
import { PluginKey, EditorState } from '@tiptap/pm/state';
|
||||
import { EditorView } from '@tiptap/pm/view';
|
||||
|
||||
interface BubbleMenuPluginProps {
|
||||
/**
|
||||
* The plugin key.
|
||||
* @type {PluginKey | string}
|
||||
* @default 'bubbleMenu'
|
||||
*/
|
||||
pluginKey: PluginKey | string;
|
||||
/**
|
||||
* The editor instance.
|
||||
*/
|
||||
editor: Editor;
|
||||
/**
|
||||
* The DOM element that contains your menu.
|
||||
* @type {HTMLElement}
|
||||
* @default null
|
||||
*/
|
||||
element: HTMLElement;
|
||||
/**
|
||||
* The delay in milliseconds before the menu should be updated.
|
||||
* This can be useful to prevent performance issues.
|
||||
* @type {number}
|
||||
* @default 250
|
||||
*/
|
||||
updateDelay?: number;
|
||||
/**
|
||||
* The delay in milliseconds before the menu position should be updated on window resize.
|
||||
* This can be useful to prevent performance issues.
|
||||
* @type {number}
|
||||
* @default 60
|
||||
*/
|
||||
resizeDelay?: number;
|
||||
/**
|
||||
* A function that determines whether the menu should be shown or not.
|
||||
* If this function returns `false`, the menu will be hidden, otherwise it will be shown.
|
||||
*/
|
||||
shouldShow?: ((props: {
|
||||
editor: Editor;
|
||||
element: HTMLElement;
|
||||
view: EditorView;
|
||||
state: EditorState;
|
||||
oldState?: EditorState;
|
||||
from: number;
|
||||
to: number;
|
||||
}) => boolean) | null;
|
||||
/**
|
||||
* The DOM element to append your menu to. Default is the editor's parent element.
|
||||
*
|
||||
* Sometimes the menu needs to be appended to a different DOM context due to accessibility, clipping, or z-index issues.
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
* @default null
|
||||
*/
|
||||
appendTo?: HTMLElement | (() => HTMLElement);
|
||||
/**
|
||||
* A function that returns the virtual element for the menu.
|
||||
* This is useful when the menu needs to be positioned relative to a specific DOM element.
|
||||
* @type {() => VirtualElement | null}
|
||||
* @default Position based on the selection.
|
||||
*/
|
||||
getReferencedVirtualElement?: () => VirtualElement | null;
|
||||
/**
|
||||
* The options for the bubble menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, arrow, size, autoPlacement,
|
||||
* hide, and inline middlewares.
|
||||
* @default {}
|
||||
* @see https://floating-ui.com/docs/computePosition#options
|
||||
*/
|
||||
options?: {
|
||||
strategy?: 'absolute' | 'fixed';
|
||||
placement?: 'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end';
|
||||
offset?: Parameters<typeof offset>[0] | boolean;
|
||||
flip?: Parameters<typeof flip>[0] | boolean;
|
||||
shift?: Parameters<typeof shift>[0] | boolean;
|
||||
arrow?: Parameters<typeof arrow>[0] | false;
|
||||
size?: Parameters<typeof size>[0] | boolean;
|
||||
autoPlacement?: Parameters<typeof autoPlacement>[0] | boolean;
|
||||
hide?: Parameters<typeof hide>[0] | boolean;
|
||||
inline?: Parameters<typeof inline>[0] | boolean;
|
||||
onShow?: () => void;
|
||||
onHide?: () => void;
|
||||
onUpdate?: () => void;
|
||||
onDestroy?: () => void;
|
||||
/**
|
||||
* The scrollable element that should be listened to when updating the position of the bubble menu.
|
||||
* If not provided, the window will be used.
|
||||
* @type {HTMLElement | Window}
|
||||
*/
|
||||
scrollTarget?: HTMLElement | Window;
|
||||
};
|
||||
}
|
||||
|
||||
declare const BubbleMenu: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
pluginKey: {
|
||||
type: PropType<BubbleMenuPluginProps["pluginKey"]>;
|
||||
default: undefined;
|
||||
};
|
||||
editor: {
|
||||
type: PropType<BubbleMenuPluginProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
updateDelay: {
|
||||
type: PropType<BubbleMenuPluginProps["updateDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
resizeDelay: {
|
||||
type: PropType<BubbleMenuPluginProps["resizeDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
options: {
|
||||
type: PropType<BubbleMenuPluginProps["options"]>;
|
||||
default: () => {};
|
||||
};
|
||||
appendTo: {
|
||||
type: PropType<BubbleMenuPluginProps["appendTo"]>;
|
||||
default: undefined;
|
||||
};
|
||||
shouldShow: {
|
||||
type: PropType<Exclude<Required<BubbleMenuPluginProps>["shouldShow"], null>>;
|
||||
default: null;
|
||||
};
|
||||
getReferencedVirtualElement: {
|
||||
type: PropType<Exclude<Required<BubbleMenuPluginProps>["getReferencedVirtualElement"], null>>;
|
||||
default: undefined;
|
||||
};
|
||||
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
||||
[key: string]: any;
|
||||
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
pluginKey: {
|
||||
type: PropType<BubbleMenuPluginProps["pluginKey"]>;
|
||||
default: undefined;
|
||||
};
|
||||
editor: {
|
||||
type: PropType<BubbleMenuPluginProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
updateDelay: {
|
||||
type: PropType<BubbleMenuPluginProps["updateDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
resizeDelay: {
|
||||
type: PropType<BubbleMenuPluginProps["resizeDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
options: {
|
||||
type: PropType<BubbleMenuPluginProps["options"]>;
|
||||
default: () => {};
|
||||
};
|
||||
appendTo: {
|
||||
type: PropType<BubbleMenuPluginProps["appendTo"]>;
|
||||
default: undefined;
|
||||
};
|
||||
shouldShow: {
|
||||
type: PropType<Exclude<Required<BubbleMenuPluginProps>["shouldShow"], null>>;
|
||||
default: null;
|
||||
};
|
||||
getReferencedVirtualElement: {
|
||||
type: PropType<Exclude<Required<BubbleMenuPluginProps>["getReferencedVirtualElement"], null>>;
|
||||
default: undefined;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
options: {
|
||||
strategy?: "absolute" | "fixed";
|
||||
placement?: "top" | "right" | "bottom" | "left" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end";
|
||||
offset?: Parameters<typeof _floating_ui_dom.offset>[0] | boolean;
|
||||
flip?: Parameters<typeof _floating_ui_dom.flip>[0] | boolean;
|
||||
shift?: Parameters<typeof _floating_ui_dom.shift>[0] | boolean;
|
||||
arrow?: Parameters<typeof _floating_ui_dom.arrow>[0] | false;
|
||||
size?: Parameters<typeof _floating_ui_dom.size>[0] | boolean;
|
||||
autoPlacement?: Parameters<typeof _floating_ui_dom.autoPlacement>[0] | boolean;
|
||||
hide?: Parameters<typeof _floating_ui_dom.hide>[0] | boolean;
|
||||
inline?: Parameters<typeof _floating_ui_dom.inline>[0] | boolean;
|
||||
onShow?: () => void;
|
||||
onHide?: () => void;
|
||||
onUpdate?: () => void;
|
||||
onDestroy?: () => void;
|
||||
scrollTarget?: HTMLElement | Window;
|
||||
} | undefined;
|
||||
pluginKey: string | PluginKey<any>;
|
||||
updateDelay: number | undefined;
|
||||
resizeDelay: number | undefined;
|
||||
appendTo: HTMLElement | (() => HTMLElement) | undefined;
|
||||
getReferencedVirtualElement: () => _floating_ui_dom.VirtualElement | null;
|
||||
shouldShow: (props: {
|
||||
editor: _tiptap_core.Editor;
|
||||
element: HTMLElement;
|
||||
view: prosemirror_view.EditorView;
|
||||
state: prosemirror_state.EditorState;
|
||||
oldState?: prosemirror_state.EditorState;
|
||||
from: number;
|
||||
to: number;
|
||||
}) => boolean;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
|
||||
interface FloatingMenuPluginProps {
|
||||
/**
|
||||
* The plugin key for the floating menu.
|
||||
* @default 'floatingMenu'
|
||||
*/
|
||||
pluginKey: PluginKey | string;
|
||||
/**
|
||||
* The editor instance.
|
||||
* @default null
|
||||
*/
|
||||
editor: Editor;
|
||||
/**
|
||||
* The DOM element that contains your menu.
|
||||
* @default null
|
||||
*/
|
||||
element: HTMLElement;
|
||||
/**
|
||||
* The delay in milliseconds before the menu should be updated.
|
||||
* This can be useful to prevent performance issues.
|
||||
* @type {number}
|
||||
* @default 250
|
||||
*/
|
||||
updateDelay?: number;
|
||||
/**
|
||||
* The delay in milliseconds before the menu position should be updated on window resize.
|
||||
* This can be useful to prevent performance issues.
|
||||
* @type {number}
|
||||
* @default 60
|
||||
*/
|
||||
resizeDelay?: number;
|
||||
/**
|
||||
* The DOM element to append your menu to. Default is the editor's parent element.
|
||||
*
|
||||
* Sometimes the menu needs to be appended to a different DOM context due to accessibility, clipping, or z-index issues.
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
* @default null
|
||||
*/
|
||||
appendTo?: HTMLElement | (() => HTMLElement);
|
||||
/**
|
||||
* A function that determines whether the menu should be shown or not.
|
||||
* If this function returns `false`, the menu will be hidden, otherwise it will be shown.
|
||||
*/
|
||||
shouldShow?: ((props: {
|
||||
editor: Editor;
|
||||
view: EditorView;
|
||||
state: EditorState;
|
||||
oldState?: EditorState;
|
||||
from: number;
|
||||
to: number;
|
||||
}) => boolean) | null;
|
||||
/**
|
||||
* The options for the floating menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, arrow, size, autoPlacement,
|
||||
* hide, and inline middlewares.
|
||||
* @default {}
|
||||
* @see https://floating-ui.com/docs/computePosition#options
|
||||
*/
|
||||
options?: {
|
||||
strategy?: 'absolute' | 'fixed';
|
||||
placement?: 'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end';
|
||||
offset?: Parameters<typeof offset>[0] | boolean;
|
||||
flip?: Parameters<typeof flip>[0] | boolean;
|
||||
shift?: Parameters<typeof shift>[0] | boolean;
|
||||
arrow?: Parameters<typeof arrow>[0] | false;
|
||||
size?: Parameters<typeof size>[0] | boolean;
|
||||
autoPlacement?: Parameters<typeof autoPlacement>[0] | boolean;
|
||||
hide?: Parameters<typeof hide>[0] | boolean;
|
||||
inline?: Parameters<typeof inline>[0] | boolean;
|
||||
onShow?: () => void;
|
||||
onHide?: () => void;
|
||||
onUpdate?: () => void;
|
||||
onDestroy?: () => void;
|
||||
/**
|
||||
* The scrollable element that should be listened to when updating the position of the floating menu.
|
||||
* If not provided, the window will be used.
|
||||
* @type {HTMLElement | Window}
|
||||
*/
|
||||
scrollTarget?: HTMLElement | Window;
|
||||
};
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
floatingMenu: {
|
||||
/**
|
||||
* Update the position of the floating menu.
|
||||
* @example editor.commands.updateFloatingMenuPosition()
|
||||
*/
|
||||
updateFloatingMenuPosition: () => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare const FloatingMenu: vue.DefineComponent<vue.ExtractPropTypes<{
|
||||
pluginKey: {
|
||||
type: null;
|
||||
default: undefined;
|
||||
};
|
||||
editor: {
|
||||
type: PropType<FloatingMenuPluginProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
updateDelay: {
|
||||
type: PropType<FloatingMenuPluginProps["updateDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
resizeDelay: {
|
||||
type: PropType<FloatingMenuPluginProps["resizeDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
options: {
|
||||
type: PropType<FloatingMenuPluginProps["options"]>;
|
||||
default: () => {};
|
||||
};
|
||||
appendTo: {
|
||||
type: PropType<FloatingMenuPluginProps["appendTo"]>;
|
||||
default: undefined;
|
||||
};
|
||||
shouldShow: {
|
||||
type: PropType<Exclude<Required<FloatingMenuPluginProps>["shouldShow"], null>>;
|
||||
default: null;
|
||||
};
|
||||
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
||||
[key: string]: any;
|
||||
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
||||
pluginKey: {
|
||||
type: null;
|
||||
default: undefined;
|
||||
};
|
||||
editor: {
|
||||
type: PropType<FloatingMenuPluginProps["editor"]>;
|
||||
required: true;
|
||||
};
|
||||
updateDelay: {
|
||||
type: PropType<FloatingMenuPluginProps["updateDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
resizeDelay: {
|
||||
type: PropType<FloatingMenuPluginProps["resizeDelay"]>;
|
||||
default: undefined;
|
||||
};
|
||||
options: {
|
||||
type: PropType<FloatingMenuPluginProps["options"]>;
|
||||
default: () => {};
|
||||
};
|
||||
appendTo: {
|
||||
type: PropType<FloatingMenuPluginProps["appendTo"]>;
|
||||
default: undefined;
|
||||
};
|
||||
shouldShow: {
|
||||
type: PropType<Exclude<Required<FloatingMenuPluginProps>["shouldShow"], null>>;
|
||||
default: null;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
options: {
|
||||
strategy?: "absolute" | "fixed";
|
||||
placement?: "top" | "right" | "bottom" | "left" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end";
|
||||
offset?: Parameters<typeof _floating_ui_dom.offset>[0] | boolean;
|
||||
flip?: Parameters<typeof _floating_ui_dom.flip>[0] | boolean;
|
||||
shift?: Parameters<typeof _floating_ui_dom.shift>[0] | boolean;
|
||||
arrow?: Parameters<typeof _floating_ui_dom.arrow>[0] | false;
|
||||
size?: Parameters<typeof _floating_ui_dom.size>[0] | boolean;
|
||||
autoPlacement?: Parameters<typeof _floating_ui_dom.autoPlacement>[0] | boolean;
|
||||
hide?: Parameters<typeof _floating_ui_dom.hide>[0] | boolean;
|
||||
inline?: Parameters<typeof _floating_ui_dom.inline>[0] | boolean;
|
||||
onShow?: () => void;
|
||||
onHide?: () => void;
|
||||
onUpdate?: () => void;
|
||||
onDestroy?: () => void;
|
||||
scrollTarget?: HTMLElement | Window;
|
||||
} | undefined;
|
||||
pluginKey: any;
|
||||
updateDelay: number | undefined;
|
||||
resizeDelay: number | undefined;
|
||||
appendTo: HTMLElement | (() => HTMLElement) | undefined;
|
||||
shouldShow: (props: {
|
||||
editor: _tiptap_core.Editor;
|
||||
view: prosemirror_view.EditorView;
|
||||
state: prosemirror_state.EditorState;
|
||||
oldState?: prosemirror_state.EditorState;
|
||||
from: number;
|
||||
to: number;
|
||||
}) => boolean;
|
||||
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
||||
|
||||
export { BubbleMenu, FloatingMenu };
|
||||
865
node_modules/@tiptap/vue-3/dist/menus/index.js
generated
vendored
Normal file
865
node_modules/@tiptap/vue-3/dist/menus/index.js
generated
vendored
Normal file
@@ -0,0 +1,865 @@
|
||||
// ../extension-bubble-menu/src/bubble-menu-plugin.ts
|
||||
import {
|
||||
arrow,
|
||||
autoPlacement,
|
||||
computePosition,
|
||||
flip,
|
||||
hide,
|
||||
inline,
|
||||
offset,
|
||||
shift,
|
||||
size
|
||||
} from "@floating-ui/dom";
|
||||
import { isTextSelection, posToDOMRect } from "@tiptap/core";
|
||||
import { NodeSelection, Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
import { CellSelection } from "@tiptap/pm/tables";
|
||||
function combineDOMRects(rect1, rect2) {
|
||||
const top = Math.min(rect1.top, rect2.top);
|
||||
const bottom = Math.max(rect1.bottom, rect2.bottom);
|
||||
const left = Math.min(rect1.left, rect2.left);
|
||||
const right = Math.max(rect1.right, rect2.right);
|
||||
const width = right - left;
|
||||
const height = bottom - top;
|
||||
const x = left;
|
||||
const y = top;
|
||||
return new DOMRect(x, y, width, height);
|
||||
}
|
||||
var BubbleMenuView = class {
|
||||
constructor({
|
||||
editor,
|
||||
element,
|
||||
view,
|
||||
pluginKey = "bubbleMenu",
|
||||
updateDelay = 250,
|
||||
resizeDelay = 60,
|
||||
shouldShow,
|
||||
appendTo,
|
||||
getReferencedVirtualElement,
|
||||
options
|
||||
}) {
|
||||
this.preventHide = false;
|
||||
this.isVisible = false;
|
||||
this.scrollTarget = window;
|
||||
this.floatingUIOptions = {
|
||||
strategy: "absolute",
|
||||
placement: "top",
|
||||
offset: 8,
|
||||
flip: {},
|
||||
shift: {},
|
||||
arrow: false,
|
||||
size: false,
|
||||
autoPlacement: false,
|
||||
hide: false,
|
||||
inline: false,
|
||||
onShow: void 0,
|
||||
onHide: void 0,
|
||||
onUpdate: void 0,
|
||||
onDestroy: void 0
|
||||
};
|
||||
this.shouldShow = ({ view, state, from, to }) => {
|
||||
const { doc, selection } = state;
|
||||
const { empty } = selection;
|
||||
const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(state.selection);
|
||||
const isChildOfMenu = this.element.contains(document.activeElement);
|
||||
const hasEditorFocus = view.hasFocus() || isChildOfMenu;
|
||||
if (!hasEditorFocus || empty || isEmptyTextBlock || !this.editor.isEditable) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
this.mousedownHandler = () => {
|
||||
this.preventHide = true;
|
||||
};
|
||||
this.dragstartHandler = () => {
|
||||
this.hide();
|
||||
};
|
||||
/**
|
||||
* Handles the window resize event to update the position of the bubble menu.
|
||||
* It uses a debounce mechanism to prevent excessive updates.
|
||||
* The delay is defined by the `resizeDelay` property.
|
||||
*/
|
||||
this.resizeHandler = () => {
|
||||
if (this.resizeDebounceTimer) {
|
||||
clearTimeout(this.resizeDebounceTimer);
|
||||
}
|
||||
this.resizeDebounceTimer = window.setTimeout(() => {
|
||||
this.updatePosition();
|
||||
}, this.resizeDelay);
|
||||
};
|
||||
this.focusHandler = () => {
|
||||
setTimeout(() => this.update(this.editor.view));
|
||||
};
|
||||
this.blurHandler = ({ event }) => {
|
||||
var _a;
|
||||
if (this.editor.isDestroyed) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
if (this.preventHide) {
|
||||
this.preventHide = false;
|
||||
return;
|
||||
}
|
||||
if ((event == null ? void 0 : event.relatedTarget) && ((_a = this.element.parentNode) == null ? void 0 : _a.contains(event.relatedTarget))) {
|
||||
return;
|
||||
}
|
||||
if ((event == null ? void 0 : event.relatedTarget) === this.editor.view.dom) {
|
||||
return;
|
||||
}
|
||||
this.hide();
|
||||
};
|
||||
this.handleDebouncedUpdate = (view, oldState) => {
|
||||
const selectionChanged = !(oldState == null ? void 0 : oldState.selection.eq(view.state.selection));
|
||||
const docChanged = !(oldState == null ? void 0 : oldState.doc.eq(view.state.doc));
|
||||
if (!selectionChanged && !docChanged) {
|
||||
return;
|
||||
}
|
||||
if (this.updateDebounceTimer) {
|
||||
clearTimeout(this.updateDebounceTimer);
|
||||
}
|
||||
this.updateDebounceTimer = window.setTimeout(() => {
|
||||
this.updateHandler(view, selectionChanged, docChanged, oldState);
|
||||
}, this.updateDelay);
|
||||
};
|
||||
this.updateHandler = (view, selectionChanged, docChanged, oldState) => {
|
||||
const { composing } = view;
|
||||
const isSame = !selectionChanged && !docChanged;
|
||||
if (composing || isSame) {
|
||||
return;
|
||||
}
|
||||
const shouldShow = this.getShouldShow(oldState);
|
||||
if (!shouldShow) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
this.updatePosition();
|
||||
this.show();
|
||||
};
|
||||
/**
|
||||
* Handles the transaction event to update the position of the bubble menu.
|
||||
* This allows external code to trigger a position update via:
|
||||
* `editor.view.dispatch(editor.state.tr.setMeta(pluginKey, 'updatePosition'))`
|
||||
* The `pluginKey` defaults to `bubbleMenu`
|
||||
*/
|
||||
this.transactionHandler = ({ transaction: tr }) => {
|
||||
const meta = tr.getMeta(this.pluginKey);
|
||||
if (meta === "updatePosition") {
|
||||
this.updatePosition();
|
||||
} else if (meta && typeof meta === "object" && meta.type === "updateOptions") {
|
||||
this.updateOptions(meta.options);
|
||||
}
|
||||
};
|
||||
var _a;
|
||||
this.editor = editor;
|
||||
this.element = element;
|
||||
this.view = view;
|
||||
this.pluginKey = pluginKey;
|
||||
this.updateDelay = updateDelay;
|
||||
this.resizeDelay = resizeDelay;
|
||||
this.appendTo = appendTo;
|
||||
this.scrollTarget = (_a = options == null ? void 0 : options.scrollTarget) != null ? _a : window;
|
||||
this.getReferencedVirtualElement = getReferencedVirtualElement;
|
||||
this.floatingUIOptions = {
|
||||
...this.floatingUIOptions,
|
||||
...options
|
||||
};
|
||||
this.element.tabIndex = 0;
|
||||
if (shouldShow) {
|
||||
this.shouldShow = shouldShow;
|
||||
}
|
||||
this.element.addEventListener("mousedown", this.mousedownHandler, { capture: true });
|
||||
this.view.dom.addEventListener("dragstart", this.dragstartHandler);
|
||||
this.editor.on("focus", this.focusHandler);
|
||||
this.editor.on("blur", this.blurHandler);
|
||||
this.editor.on("transaction", this.transactionHandler);
|
||||
window.addEventListener("resize", this.resizeHandler);
|
||||
this.scrollTarget.addEventListener("scroll", this.resizeHandler);
|
||||
this.update(view, view.state);
|
||||
if (this.getShouldShow()) {
|
||||
this.show();
|
||||
this.updatePosition();
|
||||
}
|
||||
}
|
||||
get middlewares() {
|
||||
const middlewares = [];
|
||||
if (this.floatingUIOptions.flip) {
|
||||
middlewares.push(flip(typeof this.floatingUIOptions.flip !== "boolean" ? this.floatingUIOptions.flip : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.shift) {
|
||||
middlewares.push(
|
||||
shift(typeof this.floatingUIOptions.shift !== "boolean" ? this.floatingUIOptions.shift : void 0)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.offset) {
|
||||
middlewares.push(
|
||||
offset(typeof this.floatingUIOptions.offset !== "boolean" ? this.floatingUIOptions.offset : void 0)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.arrow) {
|
||||
middlewares.push(arrow(this.floatingUIOptions.arrow));
|
||||
}
|
||||
if (this.floatingUIOptions.size) {
|
||||
middlewares.push(size(typeof this.floatingUIOptions.size !== "boolean" ? this.floatingUIOptions.size : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.autoPlacement) {
|
||||
middlewares.push(
|
||||
autoPlacement(
|
||||
typeof this.floatingUIOptions.autoPlacement !== "boolean" ? this.floatingUIOptions.autoPlacement : void 0
|
||||
)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.hide) {
|
||||
middlewares.push(hide(typeof this.floatingUIOptions.hide !== "boolean" ? this.floatingUIOptions.hide : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.inline) {
|
||||
middlewares.push(
|
||||
inline(typeof this.floatingUIOptions.inline !== "boolean" ? this.floatingUIOptions.inline : void 0)
|
||||
);
|
||||
}
|
||||
return middlewares;
|
||||
}
|
||||
get virtualElement() {
|
||||
var _a, _b, _c;
|
||||
const { selection } = this.editor.state;
|
||||
const referencedVirtualElement = (_a = this.getReferencedVirtualElement) == null ? void 0 : _a.call(this);
|
||||
if (referencedVirtualElement) {
|
||||
return referencedVirtualElement;
|
||||
}
|
||||
if (!((_c = (_b = this.view) == null ? void 0 : _b.dom) == null ? void 0 : _c.parentNode)) {
|
||||
return;
|
||||
}
|
||||
const domRect = posToDOMRect(this.view, selection.from, selection.to);
|
||||
let virtualElement = {
|
||||
getBoundingClientRect: () => domRect,
|
||||
getClientRects: () => [domRect]
|
||||
};
|
||||
if (selection instanceof NodeSelection) {
|
||||
let node = this.view.nodeDOM(selection.from);
|
||||
const nodeViewWrapper = node.dataset.nodeViewWrapper ? node : node.querySelector("[data-node-view-wrapper]");
|
||||
if (nodeViewWrapper) {
|
||||
node = nodeViewWrapper;
|
||||
}
|
||||
if (node) {
|
||||
virtualElement = {
|
||||
getBoundingClientRect: () => node.getBoundingClientRect(),
|
||||
getClientRects: () => [node.getBoundingClientRect()]
|
||||
};
|
||||
}
|
||||
}
|
||||
if (selection instanceof CellSelection) {
|
||||
const { $anchorCell, $headCell } = selection;
|
||||
const from = $anchorCell ? $anchorCell.pos : $headCell.pos;
|
||||
const to = $headCell ? $headCell.pos : $anchorCell.pos;
|
||||
const fromDOM = this.view.nodeDOM(from);
|
||||
const toDOM = this.view.nodeDOM(to);
|
||||
if (!fromDOM || !toDOM) {
|
||||
return;
|
||||
}
|
||||
const clientRect = fromDOM === toDOM ? fromDOM.getBoundingClientRect() : combineDOMRects(
|
||||
fromDOM.getBoundingClientRect(),
|
||||
toDOM.getBoundingClientRect()
|
||||
);
|
||||
virtualElement = {
|
||||
getBoundingClientRect: () => clientRect,
|
||||
getClientRects: () => [clientRect]
|
||||
};
|
||||
}
|
||||
return virtualElement;
|
||||
}
|
||||
updatePosition() {
|
||||
const virtualElement = this.virtualElement;
|
||||
if (!virtualElement) {
|
||||
return;
|
||||
}
|
||||
computePosition(virtualElement, this.element, {
|
||||
placement: this.floatingUIOptions.placement,
|
||||
strategy: this.floatingUIOptions.strategy,
|
||||
middleware: this.middlewares
|
||||
}).then(({ x, y, strategy, middlewareData }) => {
|
||||
var _a, _b;
|
||||
if (((_a = middlewareData.hide) == null ? void 0 : _a.referenceHidden) || ((_b = middlewareData.hide) == null ? void 0 : _b.escaped)) {
|
||||
this.element.style.visibility = "hidden";
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "visible";
|
||||
this.element.style.width = "max-content";
|
||||
this.element.style.position = strategy;
|
||||
this.element.style.left = `${x}px`;
|
||||
this.element.style.top = `${y}px`;
|
||||
if (this.isVisible && this.floatingUIOptions.onUpdate) {
|
||||
this.floatingUIOptions.onUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
update(view, oldState) {
|
||||
const { state } = view;
|
||||
const hasValidSelection = state.selection.from !== state.selection.to;
|
||||
if (this.updateDelay > 0 && hasValidSelection) {
|
||||
this.handleDebouncedUpdate(view, oldState);
|
||||
return;
|
||||
}
|
||||
const selectionChanged = !(oldState == null ? void 0 : oldState.selection.eq(view.state.selection));
|
||||
const docChanged = !(oldState == null ? void 0 : oldState.doc.eq(view.state.doc));
|
||||
this.updateHandler(view, selectionChanged, docChanged, oldState);
|
||||
}
|
||||
getShouldShow(oldState) {
|
||||
var _a;
|
||||
const { state } = this.view;
|
||||
const { selection } = state;
|
||||
const { ranges } = selection;
|
||||
const from = Math.min(...ranges.map((range) => range.$from.pos));
|
||||
const to = Math.max(...ranges.map((range) => range.$to.pos));
|
||||
const shouldShow = (_a = this.shouldShow) == null ? void 0 : _a.call(this, {
|
||||
editor: this.editor,
|
||||
element: this.element,
|
||||
view: this.view,
|
||||
state,
|
||||
oldState,
|
||||
from,
|
||||
to
|
||||
});
|
||||
return shouldShow || false;
|
||||
}
|
||||
show() {
|
||||
var _a;
|
||||
if (this.isVisible) {
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "visible";
|
||||
this.element.style.opacity = "1";
|
||||
const appendToElement = typeof this.appendTo === "function" ? this.appendTo() : this.appendTo;
|
||||
(_a = appendToElement != null ? appendToElement : this.view.dom.parentElement) == null ? void 0 : _a.appendChild(this.element);
|
||||
if (this.floatingUIOptions.onShow) {
|
||||
this.floatingUIOptions.onShow();
|
||||
}
|
||||
this.isVisible = true;
|
||||
}
|
||||
hide() {
|
||||
if (!this.isVisible) {
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "hidden";
|
||||
this.element.style.opacity = "0";
|
||||
this.element.remove();
|
||||
if (this.floatingUIOptions.onHide) {
|
||||
this.floatingUIOptions.onHide();
|
||||
}
|
||||
this.isVisible = false;
|
||||
}
|
||||
updateOptions(newProps) {
|
||||
var _a;
|
||||
if (newProps.updateDelay !== void 0) {
|
||||
this.updateDelay = newProps.updateDelay;
|
||||
}
|
||||
if (newProps.resizeDelay !== void 0) {
|
||||
this.resizeDelay = newProps.resizeDelay;
|
||||
}
|
||||
if (newProps.appendTo !== void 0) {
|
||||
this.appendTo = newProps.appendTo;
|
||||
}
|
||||
if (newProps.getReferencedVirtualElement !== void 0) {
|
||||
this.getReferencedVirtualElement = newProps.getReferencedVirtualElement;
|
||||
}
|
||||
if (newProps.shouldShow !== void 0) {
|
||||
if (newProps.shouldShow) {
|
||||
this.shouldShow = newProps.shouldShow;
|
||||
}
|
||||
}
|
||||
if (newProps.options !== void 0) {
|
||||
const newScrollTarget = (_a = newProps.options.scrollTarget) != null ? _a : window;
|
||||
if (newScrollTarget !== this.scrollTarget) {
|
||||
this.scrollTarget.removeEventListener("scroll", this.resizeHandler);
|
||||
this.scrollTarget = newScrollTarget;
|
||||
this.scrollTarget.addEventListener("scroll", this.resizeHandler);
|
||||
}
|
||||
this.floatingUIOptions = {
|
||||
...this.floatingUIOptions,
|
||||
...newProps.options
|
||||
};
|
||||
}
|
||||
}
|
||||
destroy() {
|
||||
this.hide();
|
||||
this.element.removeEventListener("mousedown", this.mousedownHandler, { capture: true });
|
||||
this.view.dom.removeEventListener("dragstart", this.dragstartHandler);
|
||||
window.removeEventListener("resize", this.resizeHandler);
|
||||
this.scrollTarget.removeEventListener("scroll", this.resizeHandler);
|
||||
this.editor.off("focus", this.focusHandler);
|
||||
this.editor.off("blur", this.blurHandler);
|
||||
this.editor.off("transaction", this.transactionHandler);
|
||||
if (this.floatingUIOptions.onDestroy) {
|
||||
this.floatingUIOptions.onDestroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
var BubbleMenuPlugin = (options) => {
|
||||
return new Plugin({
|
||||
key: typeof options.pluginKey === "string" ? new PluginKey(options.pluginKey) : options.pluginKey,
|
||||
view: (view) => new BubbleMenuView({ view, ...options })
|
||||
});
|
||||
};
|
||||
|
||||
// src/menus/BubbleMenu.ts
|
||||
import { PluginKey as PluginKey2 } from "@tiptap/pm/state";
|
||||
import { defineComponent, h, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
var BubbleMenu = defineComponent({
|
||||
name: "BubbleMenu",
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
pluginKey: {
|
||||
type: [String, Object],
|
||||
default: void 0
|
||||
},
|
||||
editor: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
updateDelay: {
|
||||
type: Number,
|
||||
default: void 0
|
||||
},
|
||||
resizeDelay: {
|
||||
type: Number,
|
||||
default: void 0
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
appendTo: {
|
||||
type: [Object, Function],
|
||||
default: void 0
|
||||
},
|
||||
shouldShow: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
getReferencedVirtualElement: {
|
||||
type: Function,
|
||||
default: void 0
|
||||
}
|
||||
},
|
||||
setup(props, { slots, attrs }) {
|
||||
var _a;
|
||||
const root = ref(null);
|
||||
const resolvedPluginKey = (_a = props.pluginKey) != null ? _a : new PluginKey2("bubbleMenu");
|
||||
onMounted(() => {
|
||||
const { editor, options, resizeDelay, appendTo, shouldShow, getReferencedVirtualElement, updateDelay } = props;
|
||||
const el = root.value;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
el.style.visibility = "hidden";
|
||||
el.style.position = "absolute";
|
||||
el.remove();
|
||||
nextTick(() => {
|
||||
editor.registerPlugin(
|
||||
BubbleMenuPlugin({
|
||||
editor,
|
||||
element: el,
|
||||
options,
|
||||
pluginKey: resolvedPluginKey,
|
||||
resizeDelay,
|
||||
appendTo,
|
||||
shouldShow,
|
||||
getReferencedVirtualElement,
|
||||
updateDelay
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
const { editor } = props;
|
||||
editor.unregisterPlugin(resolvedPluginKey);
|
||||
});
|
||||
return () => {
|
||||
var _a2;
|
||||
return h("div", { ref: root, ...attrs }, (_a2 = slots.default) == null ? void 0 : _a2.call(slots));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// ../extension-floating-menu/src/floating-menu-plugin.ts
|
||||
import {
|
||||
arrow as arrow2,
|
||||
autoPlacement as autoPlacement2,
|
||||
computePosition as computePosition2,
|
||||
flip as flip2,
|
||||
hide as hide2,
|
||||
inline as inline2,
|
||||
offset as offset2,
|
||||
shift as shift2,
|
||||
size as size2
|
||||
} from "@floating-ui/dom";
|
||||
import { getText, getTextSerializersFromSchema, posToDOMRect as posToDOMRect2 } from "@tiptap/core";
|
||||
import { Plugin as Plugin2, PluginKey as PluginKey3 } from "@tiptap/pm/state";
|
||||
var FloatingMenuView = class {
|
||||
constructor({
|
||||
editor,
|
||||
element,
|
||||
view,
|
||||
pluginKey = "floatingMenu",
|
||||
updateDelay = 250,
|
||||
resizeDelay = 60,
|
||||
options,
|
||||
appendTo,
|
||||
shouldShow
|
||||
}) {
|
||||
this.preventHide = false;
|
||||
this.isVisible = false;
|
||||
this.scrollTarget = window;
|
||||
this.shouldShow = ({ view, state }) => {
|
||||
const { selection } = state;
|
||||
const { $anchor, empty } = selection;
|
||||
const isRootDepth = $anchor.depth === 1;
|
||||
const isEmptyTextBlock = $anchor.parent.isTextblock && !$anchor.parent.type.spec.code && !$anchor.parent.textContent && $anchor.parent.childCount === 0 && !this.getTextContent($anchor.parent);
|
||||
if (!view.hasFocus() || !empty || !isRootDepth || !isEmptyTextBlock || !this.editor.isEditable) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
this.floatingUIOptions = {
|
||||
strategy: "absolute",
|
||||
placement: "right",
|
||||
offset: 8,
|
||||
flip: {},
|
||||
shift: {},
|
||||
arrow: false,
|
||||
size: false,
|
||||
autoPlacement: false,
|
||||
hide: false,
|
||||
inline: false
|
||||
};
|
||||
this.updateHandler = (view, selectionChanged, docChanged, oldState) => {
|
||||
const { composing } = view;
|
||||
const isSame = !selectionChanged && !docChanged;
|
||||
if (composing || isSame) {
|
||||
return;
|
||||
}
|
||||
const shouldShow = this.getShouldShow(oldState);
|
||||
if (!shouldShow) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
this.updatePosition();
|
||||
this.show();
|
||||
};
|
||||
this.mousedownHandler = () => {
|
||||
this.preventHide = true;
|
||||
};
|
||||
this.focusHandler = () => {
|
||||
setTimeout(() => this.update(this.editor.view));
|
||||
};
|
||||
this.blurHandler = ({ event }) => {
|
||||
var _a;
|
||||
if (this.preventHide) {
|
||||
this.preventHide = false;
|
||||
return;
|
||||
}
|
||||
if ((event == null ? void 0 : event.relatedTarget) && ((_a = this.element.parentNode) == null ? void 0 : _a.contains(event.relatedTarget))) {
|
||||
return;
|
||||
}
|
||||
if ((event == null ? void 0 : event.relatedTarget) === this.editor.view.dom) {
|
||||
return;
|
||||
}
|
||||
this.hide();
|
||||
};
|
||||
/**
|
||||
* Handles the transaction event to update the position of the floating menu.
|
||||
* This allows external code to trigger a position update via:
|
||||
* `editor.view.dispatch(editor.state.tr.setMeta(pluginKey, 'updatePosition'))`
|
||||
* The `pluginKey` defaults to `floatingMenu`
|
||||
*/
|
||||
this.transactionHandler = ({ transaction: tr }) => {
|
||||
const meta = tr.getMeta(this.pluginKey);
|
||||
if (meta === "updatePosition") {
|
||||
this.updatePosition();
|
||||
} else if (meta && typeof meta === "object" && meta.type === "updateOptions") {
|
||||
this.updateOptions(meta.options);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Handles the window resize event to update the position of the floating menu.
|
||||
* It uses a debounce mechanism to prevent excessive updates.
|
||||
* The delay is defined by the `resizeDelay` property.
|
||||
*/
|
||||
this.resizeHandler = () => {
|
||||
if (this.resizeDebounceTimer) {
|
||||
clearTimeout(this.resizeDebounceTimer);
|
||||
}
|
||||
this.resizeDebounceTimer = window.setTimeout(() => {
|
||||
this.updatePosition();
|
||||
}, this.resizeDelay);
|
||||
};
|
||||
var _a;
|
||||
this.editor = editor;
|
||||
this.element = element;
|
||||
this.view = view;
|
||||
this.pluginKey = pluginKey;
|
||||
this.updateDelay = updateDelay;
|
||||
this.resizeDelay = resizeDelay;
|
||||
this.appendTo = appendTo;
|
||||
this.scrollTarget = (_a = options == null ? void 0 : options.scrollTarget) != null ? _a : window;
|
||||
this.floatingUIOptions = {
|
||||
...this.floatingUIOptions,
|
||||
...options
|
||||
};
|
||||
this.element.tabIndex = 0;
|
||||
if (shouldShow) {
|
||||
this.shouldShow = shouldShow;
|
||||
}
|
||||
this.element.addEventListener("mousedown", this.mousedownHandler, { capture: true });
|
||||
this.editor.on("focus", this.focusHandler);
|
||||
this.editor.on("blur", this.blurHandler);
|
||||
this.editor.on("transaction", this.transactionHandler);
|
||||
window.addEventListener("resize", this.resizeHandler);
|
||||
this.scrollTarget.addEventListener("scroll", this.resizeHandler);
|
||||
this.update(view, view.state);
|
||||
if (this.getShouldShow()) {
|
||||
this.show();
|
||||
this.updatePosition();
|
||||
}
|
||||
}
|
||||
getTextContent(node) {
|
||||
return getText(node, { textSerializers: getTextSerializersFromSchema(this.editor.schema) });
|
||||
}
|
||||
get middlewares() {
|
||||
const middlewares = [];
|
||||
if (this.floatingUIOptions.flip) {
|
||||
middlewares.push(flip2(typeof this.floatingUIOptions.flip !== "boolean" ? this.floatingUIOptions.flip : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.shift) {
|
||||
middlewares.push(
|
||||
shift2(typeof this.floatingUIOptions.shift !== "boolean" ? this.floatingUIOptions.shift : void 0)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.offset) {
|
||||
middlewares.push(
|
||||
offset2(typeof this.floatingUIOptions.offset !== "boolean" ? this.floatingUIOptions.offset : void 0)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.arrow) {
|
||||
middlewares.push(arrow2(this.floatingUIOptions.arrow));
|
||||
}
|
||||
if (this.floatingUIOptions.size) {
|
||||
middlewares.push(size2(typeof this.floatingUIOptions.size !== "boolean" ? this.floatingUIOptions.size : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.autoPlacement) {
|
||||
middlewares.push(
|
||||
autoPlacement2(
|
||||
typeof this.floatingUIOptions.autoPlacement !== "boolean" ? this.floatingUIOptions.autoPlacement : void 0
|
||||
)
|
||||
);
|
||||
}
|
||||
if (this.floatingUIOptions.hide) {
|
||||
middlewares.push(hide2(typeof this.floatingUIOptions.hide !== "boolean" ? this.floatingUIOptions.hide : void 0));
|
||||
}
|
||||
if (this.floatingUIOptions.inline) {
|
||||
middlewares.push(
|
||||
inline2(typeof this.floatingUIOptions.inline !== "boolean" ? this.floatingUIOptions.inline : void 0)
|
||||
);
|
||||
}
|
||||
return middlewares;
|
||||
}
|
||||
getShouldShow(oldState) {
|
||||
var _a;
|
||||
const { state } = this.view;
|
||||
const { selection } = state;
|
||||
const { ranges } = selection;
|
||||
const from = Math.min(...ranges.map((range) => range.$from.pos));
|
||||
const to = Math.max(...ranges.map((range) => range.$to.pos));
|
||||
const shouldShow = (_a = this.shouldShow) == null ? void 0 : _a.call(this, {
|
||||
editor: this.editor,
|
||||
view: this.view,
|
||||
state,
|
||||
oldState,
|
||||
from,
|
||||
to
|
||||
});
|
||||
return shouldShow;
|
||||
}
|
||||
updateOptions(newProps) {
|
||||
var _a;
|
||||
if (newProps.updateDelay !== void 0) {
|
||||
this.updateDelay = newProps.updateDelay;
|
||||
}
|
||||
if (newProps.resizeDelay !== void 0) {
|
||||
this.resizeDelay = newProps.resizeDelay;
|
||||
}
|
||||
if (newProps.appendTo !== void 0) {
|
||||
this.appendTo = newProps.appendTo;
|
||||
}
|
||||
if (newProps.shouldShow !== void 0) {
|
||||
if (newProps.shouldShow) {
|
||||
this.shouldShow = newProps.shouldShow;
|
||||
}
|
||||
}
|
||||
if (newProps.options !== void 0) {
|
||||
const newScrollTarget = (_a = newProps.options.scrollTarget) != null ? _a : window;
|
||||
if (newScrollTarget !== this.scrollTarget) {
|
||||
this.scrollTarget.removeEventListener("scroll", this.resizeHandler);
|
||||
this.scrollTarget = newScrollTarget;
|
||||
this.scrollTarget.addEventListener("scroll", this.resizeHandler);
|
||||
}
|
||||
this.floatingUIOptions = {
|
||||
...this.floatingUIOptions,
|
||||
...newProps.options
|
||||
};
|
||||
}
|
||||
}
|
||||
updatePosition() {
|
||||
const { selection } = this.editor.state;
|
||||
const domRect = posToDOMRect2(this.view, selection.from, selection.to);
|
||||
const virtualElement = {
|
||||
getBoundingClientRect: () => domRect,
|
||||
getClientRects: () => [domRect]
|
||||
};
|
||||
computePosition2(virtualElement, this.element, {
|
||||
placement: this.floatingUIOptions.placement,
|
||||
strategy: this.floatingUIOptions.strategy,
|
||||
middleware: this.middlewares
|
||||
}).then(({ x, y, strategy, middlewareData }) => {
|
||||
var _a, _b;
|
||||
if (((_a = middlewareData.hide) == null ? void 0 : _a.referenceHidden) || ((_b = middlewareData.hide) == null ? void 0 : _b.escaped)) {
|
||||
this.element.style.visibility = "hidden";
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "visible";
|
||||
this.element.style.width = "max-content";
|
||||
this.element.style.position = strategy;
|
||||
this.element.style.left = `${x}px`;
|
||||
this.element.style.top = `${y}px`;
|
||||
if (this.isVisible && this.floatingUIOptions.onUpdate) {
|
||||
this.floatingUIOptions.onUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
update(view, oldState) {
|
||||
const selectionChanged = !(oldState == null ? void 0 : oldState.selection.eq(view.state.selection));
|
||||
const docChanged = !(oldState == null ? void 0 : oldState.doc.eq(view.state.doc));
|
||||
this.updateHandler(view, selectionChanged, docChanged, oldState);
|
||||
}
|
||||
show() {
|
||||
var _a;
|
||||
if (this.isVisible) {
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "visible";
|
||||
this.element.style.opacity = "1";
|
||||
const appendToElement = typeof this.appendTo === "function" ? this.appendTo() : this.appendTo;
|
||||
(_a = appendToElement != null ? appendToElement : this.view.dom.parentElement) == null ? void 0 : _a.appendChild(this.element);
|
||||
if (this.floatingUIOptions.onShow) {
|
||||
this.floatingUIOptions.onShow();
|
||||
}
|
||||
this.isVisible = true;
|
||||
}
|
||||
hide() {
|
||||
if (!this.isVisible) {
|
||||
return;
|
||||
}
|
||||
this.element.style.visibility = "hidden";
|
||||
this.element.style.opacity = "0";
|
||||
this.element.remove();
|
||||
if (this.floatingUIOptions.onHide) {
|
||||
this.floatingUIOptions.onHide();
|
||||
}
|
||||
this.isVisible = false;
|
||||
}
|
||||
destroy() {
|
||||
this.hide();
|
||||
this.element.removeEventListener("mousedown", this.mousedownHandler, { capture: true });
|
||||
window.removeEventListener("resize", this.resizeHandler);
|
||||
this.scrollTarget.removeEventListener("scroll", this.resizeHandler);
|
||||
this.editor.off("focus", this.focusHandler);
|
||||
this.editor.off("blur", this.blurHandler);
|
||||
this.editor.off("transaction", this.transactionHandler);
|
||||
if (this.floatingUIOptions.onDestroy) {
|
||||
this.floatingUIOptions.onDestroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
var FloatingMenuPlugin = (options) => {
|
||||
return new Plugin2({
|
||||
key: typeof options.pluginKey === "string" ? new PluginKey3(options.pluginKey) : options.pluginKey,
|
||||
view: (view) => new FloatingMenuView({ view, ...options })
|
||||
});
|
||||
};
|
||||
|
||||
// src/menus/FloatingMenu.ts
|
||||
import { PluginKey as PluginKey4 } from "@tiptap/pm/state";
|
||||
import { defineComponent as defineComponent2, h as h2, onBeforeUnmount as onBeforeUnmount2, onMounted as onMounted2, ref as ref2 } from "vue";
|
||||
var FloatingMenu = defineComponent2({
|
||||
name: "FloatingMenu",
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
pluginKey: {
|
||||
// TODO: TypeScript breaks :(
|
||||
// type: [String, Object as PropType<Exclude<FloatingMenuPluginProps['pluginKey'], string>>],
|
||||
type: null,
|
||||
default: void 0
|
||||
},
|
||||
editor: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
updateDelay: {
|
||||
type: Number,
|
||||
default: void 0
|
||||
},
|
||||
resizeDelay: {
|
||||
type: Number,
|
||||
default: void 0
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
appendTo: {
|
||||
type: [Object, Function],
|
||||
default: void 0
|
||||
},
|
||||
shouldShow: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
setup(props, { slots, attrs }) {
|
||||
var _a;
|
||||
const root = ref2(null);
|
||||
const resolvedPluginKey = (_a = props.pluginKey) != null ? _a : new PluginKey4("floatingMenu");
|
||||
onMounted2(() => {
|
||||
const { editor, updateDelay, resizeDelay, options, appendTo, shouldShow } = props;
|
||||
const el = root.value;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
el.style.visibility = "hidden";
|
||||
el.style.position = "absolute";
|
||||
el.remove();
|
||||
editor.registerPlugin(
|
||||
FloatingMenuPlugin({
|
||||
pluginKey: resolvedPluginKey,
|
||||
editor,
|
||||
element: el,
|
||||
updateDelay,
|
||||
resizeDelay,
|
||||
options,
|
||||
appendTo,
|
||||
shouldShow
|
||||
})
|
||||
);
|
||||
});
|
||||
onBeforeUnmount2(() => {
|
||||
const { editor } = props;
|
||||
editor.unregisterPlugin(resolvedPluginKey);
|
||||
});
|
||||
return () => {
|
||||
var _a2;
|
||||
return h2("div", { ref: root, ...attrs }, (_a2 = slots.default) == null ? void 0 : _a2.call(slots));
|
||||
};
|
||||
}
|
||||
});
|
||||
export {
|
||||
BubbleMenu,
|
||||
FloatingMenu
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
node_modules/@tiptap/vue-3/dist/menus/index.js.map
generated
vendored
Normal file
1
node_modules/@tiptap/vue-3/dist/menus/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
66
node_modules/@tiptap/vue-3/package.json
generated
vendored
Normal file
66
node_modules/@tiptap/vue-3/package.json
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "@tiptap/vue-3",
|
||||
"description": "Vue components for tiptap",
|
||||
"version": "3.21.0",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
"tiptap vue components"
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": {
|
||||
"import": "./dist/index.d.ts",
|
||||
"require": "./dist/index.d.cts"
|
||||
},
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"./menus": {
|
||||
"types": {
|
||||
"import": "./dist/menus/index.d.ts",
|
||||
"require": "./dist/menus/index.d.cts"
|
||||
},
|
||||
"import": "./dist/menus/index.js",
|
||||
"require": "./dist/menus/index.cjs"
|
||||
}
|
||||
},
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"vue": "^3.5.13",
|
||||
"@tiptap/core": "^3.21.0",
|
||||
"@tiptap/pm": "^3.21.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tiptap/extension-bubble-menu": "^3.21.0",
|
||||
"@tiptap/extension-floating-menu": "^3.21.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0",
|
||||
"@floating-ui/dom": "^1.0.0",
|
||||
"@tiptap/core": "^3.21.0",
|
||||
"@tiptap/pm": "^3.21.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ueberdosis/tiptap",
|
||||
"directory": "packages/vue-3"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"lint": "prettier ./src/ --check && eslint --cache --quiet --no-error-on-unmatched-pattern ./src/"
|
||||
}
|
||||
}
|
||||
93
node_modules/@tiptap/vue-3/src/Editor.ts
generated
vendored
Normal file
93
node_modules/@tiptap/vue-3/src/Editor.ts
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import type { EditorOptions, Storage } from '@tiptap/core'
|
||||
import { Editor as CoreEditor } from '@tiptap/core'
|
||||
import type { EditorState, Plugin, PluginKey } from '@tiptap/pm/state'
|
||||
import type { AppContext, ComponentInternalInstance, ComponentPublicInstance, Ref } from 'vue'
|
||||
import { customRef, markRaw } from 'vue'
|
||||
|
||||
function useDebouncedRef<T>(value: T) {
|
||||
return customRef<T>((track, trigger) => {
|
||||
return {
|
||||
get() {
|
||||
track()
|
||||
return value
|
||||
},
|
||||
set(newValue) {
|
||||
// update state
|
||||
value = newValue
|
||||
|
||||
// update view as soon as possible
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
trigger()
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export type ContentComponent = ComponentInternalInstance & {
|
||||
ctx: ComponentPublicInstance
|
||||
}
|
||||
|
||||
export class Editor extends CoreEditor {
|
||||
private reactiveState: Ref<EditorState>
|
||||
|
||||
private reactiveExtensionStorage: Ref<Storage>
|
||||
|
||||
public contentComponent: ContentComponent | null = null
|
||||
|
||||
public appContext: AppContext | null = null
|
||||
|
||||
constructor(options: Partial<EditorOptions> = {}) {
|
||||
super(options)
|
||||
|
||||
this.reactiveState = useDebouncedRef(this.view.state)
|
||||
this.reactiveExtensionStorage = useDebouncedRef(this.extensionStorage)
|
||||
|
||||
this.on('beforeTransaction', ({ nextState }) => {
|
||||
this.reactiveState.value = nextState
|
||||
this.reactiveExtensionStorage.value = this.extensionStorage
|
||||
})
|
||||
|
||||
return markRaw(this) // eslint-disable-line
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this.reactiveState ? this.reactiveState.value : this.view.state
|
||||
}
|
||||
|
||||
get storage() {
|
||||
return this.reactiveExtensionStorage ? this.reactiveExtensionStorage.value : super.storage
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a ProseMirror plugin.
|
||||
*/
|
||||
public registerPlugin(
|
||||
plugin: Plugin,
|
||||
handlePlugins?: (newPlugin: Plugin, plugins: Plugin[]) => Plugin[],
|
||||
): EditorState {
|
||||
const nextState = super.registerPlugin(plugin, handlePlugins)
|
||||
|
||||
if (this.reactiveState) {
|
||||
this.reactiveState.value = nextState
|
||||
}
|
||||
|
||||
return nextState
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a ProseMirror plugin.
|
||||
*/
|
||||
public unregisterPlugin(nameOrPluginKey: string | PluginKey): EditorState | undefined {
|
||||
const nextState = super.unregisterPlugin(nameOrPluginKey)
|
||||
|
||||
if (this.reactiveState && nextState) {
|
||||
this.reactiveState.value = nextState
|
||||
}
|
||||
|
||||
return nextState
|
||||
}
|
||||
}
|
||||
77
node_modules/@tiptap/vue-3/src/EditorContent.ts
generated
vendored
Normal file
77
node_modules/@tiptap/vue-3/src/EditorContent.ts
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { PropType, Ref } from 'vue'
|
||||
import { defineComponent, getCurrentInstance, h, nextTick, onBeforeUnmount, ref, unref, watchEffect } from 'vue'
|
||||
|
||||
import type { Editor } from './Editor.js'
|
||||
|
||||
export const EditorContent = defineComponent({
|
||||
name: 'EditorContent',
|
||||
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object as PropType<Editor>,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const rootEl: Ref<Element | undefined> = ref()
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
watchEffect(() => {
|
||||
const editor = props.editor
|
||||
|
||||
if (editor && editor.options.element && rootEl.value) {
|
||||
nextTick(() => {
|
||||
if (!rootEl.value || !editor.view.dom?.parentNode) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO using the new editor.mount method might allow us to remove this
|
||||
const element = unref(rootEl.value)
|
||||
|
||||
rootEl.value.append(...editor.view.dom.parentNode.childNodes)
|
||||
|
||||
// @ts-ignore
|
||||
editor.contentComponent = instance.ctx._
|
||||
|
||||
if (instance) {
|
||||
editor.appContext = {
|
||||
...instance.appContext,
|
||||
// Vue internally uses prototype chain to forward/shadow injects across the entire component chain
|
||||
// so don't use object spread operator or 'Object.assign' and just set `provides` as is on editor's appContext
|
||||
// @ts-expect-error forward instance's 'provides' into appContext
|
||||
provides: instance.provides,
|
||||
}
|
||||
}
|
||||
|
||||
editor.setOptions({
|
||||
element,
|
||||
})
|
||||
|
||||
editor.createNodeViews()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const editor = props.editor
|
||||
|
||||
if (!editor) {
|
||||
return
|
||||
}
|
||||
|
||||
editor.contentComponent = null
|
||||
editor.appContext = null
|
||||
})
|
||||
|
||||
return { rootEl }
|
||||
},
|
||||
|
||||
render() {
|
||||
return h('div', {
|
||||
ref: (el: any) => {
|
||||
this.rootEl = el
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
21
node_modules/@tiptap/vue-3/src/NodeViewContent.ts
generated
vendored
Normal file
21
node_modules/@tiptap/vue-3/src/NodeViewContent.ts
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import { defineComponent, h } from 'vue'
|
||||
|
||||
export const NodeViewContent = defineComponent({
|
||||
name: 'NodeViewContent',
|
||||
|
||||
props: {
|
||||
as: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
return h(this.as, {
|
||||
style: {
|
||||
whiteSpace: 'pre-wrap',
|
||||
},
|
||||
'data-node-view-content': '',
|
||||
})
|
||||
},
|
||||
})
|
||||
31
node_modules/@tiptap/vue-3/src/NodeViewWrapper.ts
generated
vendored
Normal file
31
node_modules/@tiptap/vue-3/src/NodeViewWrapper.ts
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import { defineComponent, h } from 'vue'
|
||||
|
||||
export const NodeViewWrapper = defineComponent({
|
||||
name: 'NodeViewWrapper',
|
||||
|
||||
props: {
|
||||
as: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
},
|
||||
|
||||
inject: ['onDragStart', 'decorationClasses'],
|
||||
|
||||
render() {
|
||||
return h(
|
||||
this.as,
|
||||
{
|
||||
// @ts-ignore
|
||||
class: this.decorationClasses,
|
||||
style: {
|
||||
whiteSpace: 'normal',
|
||||
},
|
||||
'data-node-view-wrapper': '',
|
||||
// @ts-ignore (https://github.com/vuejs/vue-next/issues/3031)
|
||||
onDragstart: this.onDragStart,
|
||||
},
|
||||
this.$slots.default?.(),
|
||||
)
|
||||
},
|
||||
})
|
||||
130
node_modules/@tiptap/vue-3/src/VueMarkViewRenderer.ts
generated
vendored
Normal file
130
node_modules/@tiptap/vue-3/src/VueMarkViewRenderer.ts
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import type { MarkViewProps, MarkViewRenderer, MarkViewRendererOptions } from '@tiptap/core'
|
||||
import { MarkView } from '@tiptap/core'
|
||||
import type { Component, PropType } from 'vue'
|
||||
import { defineComponent, h, toRaw } from 'vue'
|
||||
|
||||
import type { Editor } from './Editor.js'
|
||||
import { VueRenderer } from './VueRenderer.js'
|
||||
|
||||
export interface VueMarkViewRendererOptions extends MarkViewRendererOptions {
|
||||
as?: string
|
||||
className?: string
|
||||
attrs?: { [key: string]: string }
|
||||
}
|
||||
|
||||
export const markViewProps = {
|
||||
editor: {
|
||||
type: Object as PropType<MarkViewProps['editor']>,
|
||||
required: true as const,
|
||||
},
|
||||
mark: {
|
||||
type: Object as PropType<MarkViewProps['mark']>,
|
||||
required: true as const,
|
||||
},
|
||||
extension: {
|
||||
type: Object as PropType<MarkViewProps['extension']>,
|
||||
required: true as const,
|
||||
},
|
||||
inline: {
|
||||
type: Boolean as PropType<MarkViewProps['inline']>,
|
||||
required: true as const,
|
||||
},
|
||||
view: {
|
||||
type: Object as PropType<MarkViewProps['view']>,
|
||||
required: true as const,
|
||||
},
|
||||
updateAttributes: {
|
||||
type: Function as PropType<MarkViewProps['updateAttributes']>,
|
||||
required: true as const,
|
||||
},
|
||||
HTMLAttributes: {
|
||||
type: Object as PropType<MarkViewProps['HTMLAttributes']>,
|
||||
required: true as const,
|
||||
},
|
||||
}
|
||||
|
||||
export const MarkViewContent = defineComponent({
|
||||
name: 'MarkViewContent',
|
||||
|
||||
props: {
|
||||
as: {
|
||||
type: String,
|
||||
default: 'span',
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
return h(this.as, {
|
||||
style: {
|
||||
whiteSpace: 'inherit',
|
||||
},
|
||||
'data-mark-view-content': '',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export class VueMarkView extends MarkView<Component, VueMarkViewRendererOptions> {
|
||||
renderer: VueRenderer
|
||||
|
||||
constructor(component: Component, props: MarkViewProps, options?: Partial<VueMarkViewRendererOptions>) {
|
||||
super(component, props, options)
|
||||
|
||||
const componentProps = { ...props, updateAttributes: this.updateAttributes.bind(this) } satisfies MarkViewProps
|
||||
|
||||
// Create extended component with provide
|
||||
const extendedComponent = defineComponent({
|
||||
extends: { ...component },
|
||||
props: Object.keys(componentProps),
|
||||
template: (this.component as any).template,
|
||||
setup: reactiveProps => {
|
||||
return (component as any).setup?.(reactiveProps, {
|
||||
expose: () => undefined,
|
||||
})
|
||||
},
|
||||
// Add support for scoped styles
|
||||
__scopeId: (component as any).__scopeId,
|
||||
__cssModules: (component as any).__cssModules,
|
||||
__name: (component as any).__name,
|
||||
__file: (component as any).__file,
|
||||
})
|
||||
this.renderer = new VueRenderer(extendedComponent, {
|
||||
editor: this.editor,
|
||||
props: componentProps,
|
||||
})
|
||||
}
|
||||
|
||||
get dom() {
|
||||
return this.renderer.element as HTMLElement
|
||||
}
|
||||
|
||||
get contentDOM() {
|
||||
return this.dom.querySelector('[data-mark-view-content]') as HTMLElement | null
|
||||
}
|
||||
|
||||
updateAttributes(attrs: Record<string, any>): void {
|
||||
// since this.mark is now an proxy, we need to get the actual mark from it
|
||||
const unproxiedMark = toRaw(this.mark)
|
||||
super.updateAttributes(attrs, unproxiedMark)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.renderer.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
export function VueMarkViewRenderer(
|
||||
component: Component,
|
||||
options: Partial<VueMarkViewRendererOptions> = {},
|
||||
): MarkViewRenderer {
|
||||
return props => {
|
||||
// try to get the parent component
|
||||
// this is important for vue devtools to show the component hierarchy correctly
|
||||
// maybe it’s `undefined` because <editor-content> isn’t rendered yet
|
||||
if (!(props.editor as Editor).contentComponent) {
|
||||
return {} as unknown as MarkView<any, any>
|
||||
}
|
||||
|
||||
return new VueMarkView(component, props, options)
|
||||
}
|
||||
}
|
||||
317
node_modules/@tiptap/vue-3/src/VueNodeViewRenderer.ts
generated
vendored
Normal file
317
node_modules/@tiptap/vue-3/src/VueNodeViewRenderer.ts
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import type { DecorationWithType, NodeViewProps, NodeViewRenderer, NodeViewRendererOptions } from '@tiptap/core'
|
||||
import { NodeView } from '@tiptap/core'
|
||||
import type { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||
import type { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
|
||||
import type { Component, PropType, Ref } from 'vue'
|
||||
import { defineComponent, provide, ref } from 'vue'
|
||||
|
||||
import type { Editor } from './Editor.js'
|
||||
import { VueRenderer } from './VueRenderer.js'
|
||||
|
||||
export const nodeViewProps = {
|
||||
editor: {
|
||||
type: Object as PropType<NodeViewProps['editor']>,
|
||||
required: true as const,
|
||||
},
|
||||
node: {
|
||||
type: Object as PropType<NodeViewProps['node']>,
|
||||
required: true as const,
|
||||
},
|
||||
decorations: {
|
||||
type: Object as PropType<NodeViewProps['decorations']>,
|
||||
required: true as const,
|
||||
},
|
||||
selected: {
|
||||
type: Boolean as PropType<NodeViewProps['selected']>,
|
||||
required: true as const,
|
||||
},
|
||||
extension: {
|
||||
type: Object as PropType<NodeViewProps['extension']>,
|
||||
required: true as const,
|
||||
},
|
||||
getPos: {
|
||||
type: Function as PropType<NodeViewProps['getPos']>,
|
||||
required: true as const,
|
||||
},
|
||||
updateAttributes: {
|
||||
type: Function as PropType<NodeViewProps['updateAttributes']>,
|
||||
required: true as const,
|
||||
},
|
||||
deleteNode: {
|
||||
type: Function as PropType<NodeViewProps['deleteNode']>,
|
||||
required: true as const,
|
||||
},
|
||||
view: {
|
||||
type: Object as PropType<NodeViewProps['view']>,
|
||||
required: true as const,
|
||||
},
|
||||
innerDecorations: {
|
||||
type: Object as PropType<NodeViewProps['innerDecorations']>,
|
||||
required: true as const,
|
||||
},
|
||||
HTMLAttributes: {
|
||||
type: Object as PropType<NodeViewProps['HTMLAttributes']>,
|
||||
required: true as const,
|
||||
},
|
||||
}
|
||||
|
||||
export interface VueNodeViewRendererOptions extends NodeViewRendererOptions {
|
||||
update:
|
||||
| ((props: {
|
||||
oldNode: ProseMirrorNode
|
||||
oldDecorations: readonly Decoration[]
|
||||
oldInnerDecorations: DecorationSource
|
||||
newNode: ProseMirrorNode
|
||||
newDecorations: readonly Decoration[]
|
||||
innerDecorations: DecorationSource
|
||||
updateProps: () => void
|
||||
}) => boolean)
|
||||
| null
|
||||
}
|
||||
|
||||
class VueNodeView extends NodeView<Component, Editor, VueNodeViewRendererOptions> {
|
||||
renderer!: VueRenderer
|
||||
|
||||
decorationClasses!: Ref<string>
|
||||
|
||||
private cachedExtensionWithSyncedStorage: NodeViewProps['extension'] | null = null
|
||||
|
||||
/**
|
||||
* Returns a proxy of the extension that redirects storage access to the editor's mutable storage.
|
||||
* This preserves the original prototype chain (instanceof checks, methods like configure/extend work).
|
||||
* Cached to avoid proxy creation on every update.
|
||||
*/
|
||||
get extensionWithSyncedStorage(): NodeViewProps['extension'] {
|
||||
if (!this.cachedExtensionWithSyncedStorage) {
|
||||
const editor = this.editor
|
||||
const extension = this.extension
|
||||
|
||||
this.cachedExtensionWithSyncedStorage = new Proxy(extension, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === 'storage') {
|
||||
return editor.storage[extension.name as keyof typeof editor.storage] ?? {}
|
||||
}
|
||||
return Reflect.get(target, prop, receiver)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return this.cachedExtensionWithSyncedStorage
|
||||
}
|
||||
|
||||
mount() {
|
||||
const props = {
|
||||
editor: this.editor,
|
||||
node: this.node,
|
||||
decorations: this.decorations as DecorationWithType[],
|
||||
innerDecorations: this.innerDecorations,
|
||||
view: this.view,
|
||||
selected: false,
|
||||
extension: this.extensionWithSyncedStorage,
|
||||
HTMLAttributes: this.HTMLAttributes,
|
||||
getPos: () => this.getPos(),
|
||||
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
||||
deleteNode: () => this.deleteNode(),
|
||||
} satisfies NodeViewProps
|
||||
|
||||
const onDragStart = this.onDragStart.bind(this)
|
||||
|
||||
this.decorationClasses = ref(this.getDecorationClasses())
|
||||
|
||||
const extendedComponent = defineComponent({
|
||||
extends: { ...this.component },
|
||||
props: Object.keys(props),
|
||||
template: (this.component as any).template,
|
||||
setup: reactiveProps => {
|
||||
provide('onDragStart', onDragStart)
|
||||
provide('decorationClasses', this.decorationClasses)
|
||||
|
||||
return (this.component as any).setup?.(reactiveProps, {
|
||||
expose: () => undefined,
|
||||
})
|
||||
},
|
||||
// add support for scoped styles
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__scopeId: this.component.__scopeId,
|
||||
// add support for CSS Modules
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__cssModules: this.component.__cssModules,
|
||||
// add support for vue devtools
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__name: this.component.__name,
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
__file: this.component.__file,
|
||||
})
|
||||
|
||||
this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this)
|
||||
this.editor.on('selectionUpdate', this.handleSelectionUpdate)
|
||||
|
||||
this.renderer = new VueRenderer(extendedComponent, {
|
||||
editor: this.editor,
|
||||
props,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DOM element.
|
||||
* This is the element that will be used to display the node view.
|
||||
*/
|
||||
get dom() {
|
||||
if (!this.renderer.element || !this.renderer.element.hasAttribute('data-node-view-wrapper')) {
|
||||
throw Error('Please use the NodeViewWrapper component for your node view.')
|
||||
}
|
||||
|
||||
return this.renderer.element as HTMLElement
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content DOM element.
|
||||
* This is the element that will be used to display the rich-text content of the node.
|
||||
*/
|
||||
get contentDOM() {
|
||||
if (this.node.isLeaf) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.dom.querySelector('[data-node-view-content]') as HTMLElement | null
|
||||
}
|
||||
|
||||
/**
|
||||
* On editor selection update, check if the node is selected.
|
||||
* If it is, call `selectNode`, otherwise call `deselectNode`.
|
||||
*/
|
||||
handleSelectionUpdate() {
|
||||
const { from, to } = this.editor.state.selection
|
||||
const pos = this.getPos()
|
||||
|
||||
if (typeof pos !== 'number') {
|
||||
return
|
||||
}
|
||||
|
||||
if (from <= pos && to >= pos + this.node.nodeSize) {
|
||||
if (this.renderer.props.selected) {
|
||||
return
|
||||
}
|
||||
|
||||
this.selectNode()
|
||||
} else {
|
||||
if (!this.renderer.props.selected) {
|
||||
return
|
||||
}
|
||||
|
||||
this.deselectNode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On update, update the React component.
|
||||
* To prevent unnecessary updates, the `update` option can be used.
|
||||
*/
|
||||
update(node: ProseMirrorNode, decorations: readonly Decoration[], innerDecorations: DecorationSource): boolean {
|
||||
const rerenderComponent = (props?: Record<string, any>) => {
|
||||
this.decorationClasses.value = this.getDecorationClasses()
|
||||
this.renderer.updateProps(props)
|
||||
}
|
||||
|
||||
if (typeof this.options.update === 'function') {
|
||||
const oldNode = this.node
|
||||
const oldDecorations = this.decorations
|
||||
const oldInnerDecorations = this.innerDecorations
|
||||
|
||||
this.node = node
|
||||
this.decorations = decorations
|
||||
this.innerDecorations = innerDecorations
|
||||
|
||||
return this.options.update({
|
||||
oldNode,
|
||||
oldDecorations,
|
||||
newNode: node,
|
||||
newDecorations: decorations,
|
||||
oldInnerDecorations,
|
||||
innerDecorations,
|
||||
updateProps: () =>
|
||||
rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage }),
|
||||
})
|
||||
}
|
||||
|
||||
if (node.type !== this.node.type) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) {
|
||||
return true
|
||||
}
|
||||
|
||||
this.node = node
|
||||
this.decorations = decorations
|
||||
this.innerDecorations = innerDecorations
|
||||
|
||||
rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage })
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the node.
|
||||
* Add the `selected` prop and the `ProseMirror-selectednode` class.
|
||||
*/
|
||||
selectNode() {
|
||||
this.renderer.updateProps({
|
||||
selected: true,
|
||||
})
|
||||
if (this.renderer.element) {
|
||||
this.renderer.element.classList.add('ProseMirror-selectednode')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deselect the node.
|
||||
* Remove the `selected` prop and the `ProseMirror-selectednode` class.
|
||||
*/
|
||||
deselectNode() {
|
||||
this.renderer.updateProps({
|
||||
selected: false,
|
||||
})
|
||||
if (this.renderer.element) {
|
||||
this.renderer.element.classList.remove('ProseMirror-selectednode')
|
||||
}
|
||||
}
|
||||
|
||||
getDecorationClasses() {
|
||||
return (
|
||||
this.decorations
|
||||
// @ts-ignore
|
||||
.flatMap(item => item.type.attrs.class)
|
||||
.join(' ')
|
||||
)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.renderer.destroy()
|
||||
this.editor.off('selectionUpdate', this.handleSelectionUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
export function VueNodeViewRenderer(
|
||||
component: Component<NodeViewProps>,
|
||||
options?: Partial<VueNodeViewRendererOptions>,
|
||||
): NodeViewRenderer {
|
||||
return props => {
|
||||
// try to get the parent component
|
||||
// this is important for vue devtools to show the component hierarchy correctly
|
||||
// maybe it’s `undefined` because <editor-content> isn’t rendered yet
|
||||
if (!(props.editor as Editor).contentComponent) {
|
||||
return {} as unknown as ProseMirrorNodeView
|
||||
}
|
||||
// check for class-component and normalize if neccessary
|
||||
const normalizedComponent =
|
||||
typeof component === 'function' && '__vccOpts' in component ? (component.__vccOpts as Component) : component
|
||||
|
||||
return new VueNodeView(normalizedComponent, props, options)
|
||||
}
|
||||
}
|
||||
104
node_modules/@tiptap/vue-3/src/VueRenderer.ts
generated
vendored
Normal file
104
node_modules/@tiptap/vue-3/src/VueRenderer.ts
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { Editor } from '@tiptap/core'
|
||||
import type { Component, DefineComponent } from 'vue'
|
||||
import { h, markRaw, reactive, render } from 'vue'
|
||||
|
||||
import type { Editor as ExtendedEditor } from './Editor.js'
|
||||
|
||||
export interface VueRendererOptions {
|
||||
editor: Editor
|
||||
props?: Record<string, any>
|
||||
}
|
||||
|
||||
type ExtendedVNode = ReturnType<typeof h> | null
|
||||
|
||||
interface RenderedComponent {
|
||||
vNode: ExtendedVNode
|
||||
destroy: () => void
|
||||
el: Element | null
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to render Vue components inside the editor.
|
||||
*/
|
||||
export class VueRenderer {
|
||||
renderedComponent!: RenderedComponent
|
||||
|
||||
editor: ExtendedEditor
|
||||
|
||||
component: Component
|
||||
|
||||
el: Element | null
|
||||
|
||||
props: Record<string, any>
|
||||
|
||||
/**
|
||||
* Flag to track if the renderer has been destroyed, preventing queued or asynchronous renders from executing after teardown.
|
||||
*/
|
||||
destroyed = false
|
||||
|
||||
constructor(component: Component, { props = {}, editor }: VueRendererOptions) {
|
||||
this.editor = editor as ExtendedEditor
|
||||
this.component = markRaw(component)
|
||||
this.el = document.createElement('div')
|
||||
this.props = reactive(props)
|
||||
this.renderedComponent = this.renderComponent()
|
||||
}
|
||||
|
||||
get element(): Element | null {
|
||||
return this.renderedComponent.el
|
||||
}
|
||||
|
||||
get ref(): any {
|
||||
// Composition API
|
||||
if (this.renderedComponent.vNode?.component?.exposed) {
|
||||
return this.renderedComponent.vNode.component.exposed
|
||||
}
|
||||
// Option API
|
||||
return this.renderedComponent.vNode?.component?.proxy
|
||||
}
|
||||
|
||||
renderComponent() {
|
||||
if (this.destroyed) {
|
||||
return this.renderedComponent
|
||||
}
|
||||
|
||||
let vNode: ExtendedVNode = h(this.component as DefineComponent, this.props)
|
||||
|
||||
if (this.editor.appContext) {
|
||||
vNode.appContext = this.editor.appContext
|
||||
}
|
||||
if (typeof document !== 'undefined' && this.el) {
|
||||
render(vNode, this.el)
|
||||
}
|
||||
|
||||
const destroy = () => {
|
||||
if (this.el) {
|
||||
render(null, this.el)
|
||||
}
|
||||
this.el = null
|
||||
vNode = null
|
||||
}
|
||||
|
||||
return { vNode, destroy, el: this.el ? this.el.firstElementChild : null }
|
||||
}
|
||||
|
||||
updateProps(props: Record<string, any> = {}): void {
|
||||
if (this.destroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
Object.entries(props).forEach(([key, value]) => {
|
||||
this.props[key] = value
|
||||
})
|
||||
this.renderComponent()
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this.destroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
this.destroyed = true
|
||||
this.renderedComponent.destroy()
|
||||
}
|
||||
}
|
||||
9
node_modules/@tiptap/vue-3/src/index.ts
generated
vendored
Normal file
9
node_modules/@tiptap/vue-3/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
export { Editor } from './Editor.js'
|
||||
export * from './EditorContent.js'
|
||||
export * from './NodeViewContent.js'
|
||||
export * from './NodeViewWrapper.js'
|
||||
export * from './useEditor.js'
|
||||
export * from './VueMarkViewRenderer.js'
|
||||
export * from './VueNodeViewRenderer.js'
|
||||
export * from './VueRenderer.js'
|
||||
export * from '@tiptap/core'
|
||||
100
node_modules/@tiptap/vue-3/src/menus/BubbleMenu.ts
generated
vendored
Normal file
100
node_modules/@tiptap/vue-3/src/menus/BubbleMenu.ts
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
import type { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'
|
||||
import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu'
|
||||
import { PluginKey } from '@tiptap/pm/state'
|
||||
import type { PropType } from 'vue'
|
||||
import { defineComponent, h, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
export const BubbleMenu = defineComponent({
|
||||
name: 'BubbleMenu',
|
||||
|
||||
inheritAttrs: false,
|
||||
|
||||
props: {
|
||||
pluginKey: {
|
||||
type: [String, Object] as PropType<BubbleMenuPluginProps['pluginKey']>,
|
||||
default: undefined,
|
||||
},
|
||||
|
||||
editor: {
|
||||
type: Object as PropType<BubbleMenuPluginProps['editor']>,
|
||||
required: true,
|
||||
},
|
||||
|
||||
updateDelay: {
|
||||
type: Number as PropType<BubbleMenuPluginProps['updateDelay']>,
|
||||
default: undefined,
|
||||
},
|
||||
|
||||
resizeDelay: {
|
||||
type: Number as PropType<BubbleMenuPluginProps['resizeDelay']>,
|
||||
default: undefined,
|
||||
},
|
||||
|
||||
options: {
|
||||
type: Object as PropType<BubbleMenuPluginProps['options']>,
|
||||
default: () => ({}),
|
||||
},
|
||||
|
||||
appendTo: {
|
||||
type: [Object, Function] as PropType<BubbleMenuPluginProps['appendTo']>,
|
||||
default: undefined,
|
||||
},
|
||||
|
||||
shouldShow: {
|
||||
type: Function as PropType<Exclude<Required<BubbleMenuPluginProps>['shouldShow'], null>>,
|
||||
default: null,
|
||||
},
|
||||
|
||||
getReferencedVirtualElement: {
|
||||
type: Function as PropType<Exclude<Required<BubbleMenuPluginProps>['getReferencedVirtualElement'], null>>,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { slots, attrs }) {
|
||||
const root = ref<HTMLElement | null>(null)
|
||||
const resolvedPluginKey = props.pluginKey ?? new PluginKey('bubbleMenu')
|
||||
|
||||
onMounted(() => {
|
||||
const { editor, options, resizeDelay, appendTo, shouldShow, getReferencedVirtualElement, updateDelay } = props
|
||||
|
||||
const el = root.value
|
||||
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
|
||||
el.style.visibility = 'hidden'
|
||||
el.style.position = 'absolute'
|
||||
|
||||
// Remove element from DOM; plugin will re-parent it when shown
|
||||
el.remove()
|
||||
|
||||
nextTick(() => {
|
||||
editor.registerPlugin(
|
||||
BubbleMenuPlugin({
|
||||
editor,
|
||||
element: el,
|
||||
options,
|
||||
pluginKey: resolvedPluginKey,
|
||||
resizeDelay,
|
||||
appendTo,
|
||||
shouldShow,
|
||||
getReferencedVirtualElement,
|
||||
updateDelay,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const { editor } = props
|
||||
|
||||
editor.unregisterPlugin(resolvedPluginKey)
|
||||
})
|
||||
|
||||
// Vue owns this element; attrs are applied reactively by Vue
|
||||
// Plugin re-parents it when showing the menu
|
||||
return () => h('div', { ref: root, ...attrs }, slots.default?.())
|
||||
},
|
||||
})
|
||||
94
node_modules/@tiptap/vue-3/src/menus/FloatingMenu.ts
generated
vendored
Normal file
94
node_modules/@tiptap/vue-3/src/menus/FloatingMenu.ts
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
import type { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'
|
||||
import { FloatingMenuPlugin } from '@tiptap/extension-floating-menu'
|
||||
import { PluginKey } from '@tiptap/pm/state'
|
||||
import type { PropType } from 'vue'
|
||||
import { defineComponent, h, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
export const FloatingMenu = defineComponent({
|
||||
name: 'FloatingMenu',
|
||||
|
||||
inheritAttrs: false,
|
||||
|
||||
props: {
|
||||
pluginKey: {
|
||||
// TODO: TypeScript breaks :(
|
||||
// type: [String, Object as PropType<Exclude<FloatingMenuPluginProps['pluginKey'], string>>],
|
||||
type: null,
|
||||
default: undefined,
|
||||
},
|
||||
|
||||
editor: {
|
||||
type: Object as PropType<FloatingMenuPluginProps['editor']>,
|
||||
required: true,
|
||||
},
|
||||
|
||||
updateDelay: {
|
||||
type: Number as PropType<FloatingMenuPluginProps['updateDelay']>,
|
||||
default: undefined,
|
||||
},
|
||||
|
||||
resizeDelay: {
|
||||
type: Number as PropType<FloatingMenuPluginProps['resizeDelay']>,
|
||||
default: undefined,
|
||||
},
|
||||
|
||||
options: {
|
||||
type: Object as PropType<FloatingMenuPluginProps['options']>,
|
||||
default: () => ({}),
|
||||
},
|
||||
|
||||
appendTo: {
|
||||
type: [Object, Function] as PropType<FloatingMenuPluginProps['appendTo']>,
|
||||
default: undefined,
|
||||
},
|
||||
|
||||
shouldShow: {
|
||||
type: Function as PropType<Exclude<Required<FloatingMenuPluginProps>['shouldShow'], null>>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { slots, attrs }) {
|
||||
const root = ref<HTMLElement | null>(null)
|
||||
const resolvedPluginKey = props.pluginKey ?? new PluginKey('floatingMenu')
|
||||
|
||||
onMounted(() => {
|
||||
const { editor, updateDelay, resizeDelay, options, appendTo, shouldShow } = props
|
||||
|
||||
const el = root.value
|
||||
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
|
||||
el.style.visibility = 'hidden'
|
||||
el.style.position = 'absolute'
|
||||
|
||||
// Remove element from DOM; plugin will re-parent it when shown
|
||||
el.remove()
|
||||
|
||||
editor.registerPlugin(
|
||||
FloatingMenuPlugin({
|
||||
pluginKey: resolvedPluginKey,
|
||||
editor,
|
||||
element: el,
|
||||
updateDelay,
|
||||
resizeDelay,
|
||||
options,
|
||||
appendTo,
|
||||
shouldShow,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const { editor } = props
|
||||
|
||||
editor.unregisterPlugin(resolvedPluginKey)
|
||||
})
|
||||
|
||||
// Vue owns this element; attrs are applied reactively by Vue
|
||||
// Plugin re-parents it when showing the menu
|
||||
return () => h('div', { ref: root, ...attrs }, slots.default?.())
|
||||
},
|
||||
})
|
||||
2
node_modules/@tiptap/vue-3/src/menus/index.ts
generated
vendored
Normal file
2
node_modules/@tiptap/vue-3/src/menus/index.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './BubbleMenu.js'
|
||||
export * from './FloatingMenu.js'
|
||||
24
node_modules/@tiptap/vue-3/src/useEditor.ts
generated
vendored
Normal file
24
node_modules/@tiptap/vue-3/src/useEditor.ts
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { EditorOptions } from '@tiptap/core'
|
||||
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
|
||||
|
||||
import { Editor } from './Editor.js'
|
||||
|
||||
export const useEditor = (options: Partial<EditorOptions> = {}) => {
|
||||
const editor = shallowRef<Editor>()
|
||||
|
||||
onMounted(() => {
|
||||
editor.value = new Editor(options)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// Cloning root node (and its children) to avoid content being lost by destroy
|
||||
const nodes = editor.value?.view.dom?.parentNode
|
||||
const newEl = nodes?.cloneNode(true) as HTMLElement
|
||||
|
||||
nodes?.parentNode?.replaceChild(newEl, nodes)
|
||||
|
||||
editor.value?.destroy()
|
||||
})
|
||||
|
||||
return editor
|
||||
}
|
||||
Reference in New Issue
Block a user