bettertend/frontend/node_modules/@tiptap/suggestion/dist/index.umd.js

264 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/pm/state'), require('@tiptap/pm/view'), require('@tiptap/core')) :
typeof define === 'function' && define.amd ? define(['exports', '@tiptap/pm/state', '@tiptap/pm/view', '@tiptap/core'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tiptap/suggestion"] = {}, global.state, global.view, global.core));
})(this, (function (exports, state, view, core) { 'use strict';
function findSuggestionMatch(config) {
var _a;
const { char, allowSpaces: allowSpacesOption, allowToIncludeChar, allowedPrefixes, startOfLine, $position, } = config;
const allowSpaces = allowSpacesOption && !allowToIncludeChar;
const escapedChar = core.escapeForRegEx(char);
const suffix = new RegExp(`\\s${escapedChar}$`);
const prefix = startOfLine ? '^' : '';
const finalEscapedChar = allowToIncludeChar ? '' : escapedChar;
const regexp = allowSpaces
? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${finalEscapedChar}|$)`, 'gm')
: new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${finalEscapedChar}]*`, 'gm');
const text = ((_a = $position.nodeBefore) === null || _a === void 0 ? void 0 : _a.isText) && $position.nodeBefore.text;
if (!text) {
return null;
}
const textFrom = $position.pos - text.length;
const match = Array.from(text.matchAll(regexp)).pop();
if (!match || match.input === undefined || match.index === undefined) {
return null;
}
// JavaScript doesn't have lookbehinds. This hacks a check that first character
// is a space or the start of the line
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index);
const matchPrefixIsAllowed = new RegExp(`^[${allowedPrefixes === null || allowedPrefixes === void 0 ? void 0 : allowedPrefixes.join('')}\0]?$`).test(matchPrefix);
if (allowedPrefixes !== null && !matchPrefixIsAllowed) {
return null;
}
// The absolute position of the match in the document
const from = textFrom + match.index;
let to = from + match[0].length;
// Edge case handling; if spaces are allowed and we're directly in between
// two triggers
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
match[0] += ' ';
to += 1;
}
// If the $position is located within the matched substring, return that range
if (from < $position.pos && to >= $position.pos) {
return {
range: {
from,
to,
},
query: match[0].slice(char.length),
text: match[0],
};
}
return null;
}
const SuggestionPluginKey = new state.PluginKey('suggestion');
/**
* This utility allows you to create suggestions.
* @see https://tiptap.dev/api/utilities/suggestion
*/
function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allowSpaces = false, allowToIncludeChar = false, allowedPrefixes = [' '], startOfLine = false, decorationTag = 'span', decorationClass = 'suggestion', decorationContent = '', decorationEmptyClass = 'is-empty', command = () => null, items = () => [], render = () => ({}), allow = () => true, findSuggestionMatch: findSuggestionMatch$1 = findSuggestionMatch, }) {
let props;
const renderer = render === null || render === void 0 ? void 0 : render();
const plugin = new state.Plugin({
key: pluginKey,
view() {
return {
update: async (view, prevState) => {
var _a, _b, _c, _d, _e, _f, _g;
const prev = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(prevState);
const next = (_b = this.key) === null || _b === void 0 ? void 0 : _b.getState(view.state);
// See how the state changed
const moved = prev.active && next.active && prev.range.from !== next.range.from;
const started = !prev.active && next.active;
const stopped = prev.active && !next.active;
const changed = !started && !stopped && prev.query !== next.query;
const handleStart = started || (moved && changed);
const handleChange = changed || moved;
const handleExit = stopped || (moved && changed);
// Cancel when suggestion isn't active
if (!handleStart && !handleChange && !handleExit) {
return;
}
const state = handleExit && !handleStart ? prev : next;
const decorationNode = view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`);
props = {
editor,
range: state.range,
query: state.query,
text: state.text,
items: [],
command: commandProps => {
return command({
editor,
range: state.range,
props: commandProps,
});
},
decorationNode,
// virtual node for popper.js or tippy.js
// this can be used for building popups without a DOM node
clientRect: decorationNode
? () => {
var _a;
// because of `items` can be asynchrounous well search for the current decoration node
const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state); // eslint-disable-line
const currentDecorationNode = view.dom.querySelector(`[data-decoration-id="${decorationId}"]`);
return (currentDecorationNode === null || currentDecorationNode === void 0 ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
}
: null,
};
if (handleStart) {
(_c = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeStart) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
}
if (handleChange) {
(_d = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
}
if (handleChange || handleStart) {
props.items = await items({
editor,
query: state.query,
});
}
if (handleExit) {
(_e = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
}
if (handleChange) {
(_f = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _f === void 0 ? void 0 : _f.call(renderer, props);
}
if (handleStart) {
(_g = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _g === void 0 ? void 0 : _g.call(renderer, props);
}
},
destroy: () => {
var _a;
if (!props) {
return;
}
(_a = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _a === void 0 ? void 0 : _a.call(renderer, props);
},
};
},
state: {
// Initialize the plugin's internal state.
init() {
const state = {
active: false,
range: {
from: 0,
to: 0,
},
query: null,
text: null,
composing: false,
};
return state;
},
// Apply changes to the plugin state from a view transaction.
apply(transaction, prev, _oldState, state) {
const { isEditable } = editor;
const { composing } = editor.view;
const { selection } = transaction;
const { empty, from } = selection;
const next = { ...prev };
next.composing = composing;
// We can only be suggesting if the view is editable, and:
// * there is no selection, or
// * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
if (isEditable && (empty || editor.view.composing)) {
// Reset active state if we just left the previous suggestion range
if ((from < prev.range.from || from > prev.range.to)
&& !composing
&& !prev.composing) {
next.active = false;
}
// Try to match against where our cursor currently is
const match = findSuggestionMatch$1({
char,
allowSpaces,
allowToIncludeChar,
allowedPrefixes,
startOfLine,
$position: selection.$from,
});
const decorationId = `id_${Math.floor(Math.random() * 0xffffffff)}`;
// If we found a match, update the current state to show it
if (match
&& allow({
editor,
state,
range: match.range,
isActive: prev.active,
})) {
next.active = true;
next.decorationId = prev.decorationId
? prev.decorationId
: decorationId;
next.range = match.range;
next.query = match.query;
next.text = match.text;
}
else {
next.active = false;
}
}
else {
next.active = false;
}
// Make sure to empty the range if suggestion is inactive
if (!next.active) {
next.decorationId = null;
next.range = { from: 0, to: 0 };
next.query = null;
next.text = null;
}
return next;
},
},
props: {
// Call the keydown hook if suggestion is active.
handleKeyDown(view, event) {
var _a;
const { active, range } = plugin.getState(view.state);
if (!active) {
return false;
}
return ((_a = renderer === null || renderer === void 0 ? void 0 : renderer.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(renderer, { view, event, range })) || false;
},
// Setup decorator on the currently active suggestion.
decorations(state) {
const { active, range, decorationId, query, } = plugin.getState(state);
if (!active) {
return null;
}
const isEmpty = !(query === null || query === void 0 ? void 0 : query.length);
const classNames = [decorationClass];
if (isEmpty) {
classNames.push(decorationEmptyClass);
}
return view.DecorationSet.create(state.doc, [
view.Decoration.inline(range.from, range.to, {
nodeName: decorationTag,
class: classNames.join(' '),
'data-decoration-id': decorationId,
'data-decoration-content': decorationContent,
}),
]);
},
},
});
return plugin;
}
exports.Suggestion = Suggestion;
exports.SuggestionPluginKey = SuggestionPluginKey;
exports.default = Suggestion;
exports.findSuggestionMatch = findSuggestionMatch;
Object.defineProperty(exports, '__esModule', { value: true });
}));
//# sourceMappingURL=index.umd.js.map