@alkhipce/editorjs-aitext
Version:
Open AI text tool for Editor.js
315 lines (314 loc) • 11.2 kB
JavaScript
(function() {
try {
if (typeof document < "u") {
var c = document.createElement("style");
c.appendChild(document.createTextNode(".ce-paragraph{line-height:1.6em;outline:none}.ce-paragraph[data-placeholder]:empty:before{content:attr(data-placeholder);color:#707684;font-weight:400;opacity:0}.codex-editor--empty .ce-block:first-child .ce-paragraph[data-placeholder]:empty:before{opacity:1}.codex-editor--toolbox-opened .ce-block:first-child .ce-paragraph[data-placeholder]:empty:before,.codex-editor--empty .ce-block:first-child .ce-paragraph[data-placeholder]:empty:focus:before{opacity:0}.ce-paragraph p:first-of-type{margin-top:0}.ce-paragraph p:last-of-type{margin-bottom:0}")), document.head.appendChild(c);
}
} catch (e) {
console.error("vite-plugin-css-injected-by-js", e);
}
})();
const m = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M8 9V7.2C8 7.08954 8.08954 7 8.2 7L12 7M16 9V7.2C16 7.08954 15.9105 7 15.8 7L12 7M12 7L12 17M12 17H10M12 17H14"/></svg>';
/**
* Base Paragraph Block for the Editor.js.
* Represents a regular text block
*
* @author CodeX (team@codex.so)
* @copyright CodeX 2018
* @license The MIT License (MIT)
*/
class h {
/**
* Default placeholder for Paragraph Tool
*
* @returns {string}
* @class
*/
static get DEFAULT_PLACEHOLDER() {
return "";
}
/**
* Render plugin`s main Element and fill it with saved data
*
* @param {object} params - constructor params
* @param {ParagraphData} params.data - previously saved data
* @param {ParagraphConfig} params.config - user config for Tool
* @param {object} params.api - editor.js api
* @param {boolean} readOnly - read only mode flag
*/
constructor({ data: e, config: t, api: i, readOnly: s }) {
this.api = i, this.readOnly = s, this._CSS = {
block: this.api.styles.block,
wrapper: "ce-paragraph"
}, this.readOnly || (this.onKeyUp = this.onKeyUp.bind(this)), this._placeholder = t.placeholder ? t.placeholder : h.DEFAULT_PLACEHOLDER, this._data = {}, this._element = null, this._preserveBlank = t.preserveBlank !== void 0 ? t.preserveBlank : !1, this.data = e;
}
/**
* Check if text content is empty and set empty string to inner html.
* We need this because some browsers (e.g. Safari) insert <br> into empty contenteditanle elements
*
* @param {KeyboardEvent} e - key up event
*/
onKeyUp(e) {
if (e.code !== "Backspace" && e.code !== "Delete")
return;
const { textContent: t } = this._element;
t === "" && (this._element.innerHTML = "");
}
/**
* Create Tool's view
*
* @returns {HTMLElement}
* @private
*/
drawView() {
const e = document.createElement("DIV");
return e.classList.add(this._CSS.wrapper, this._CSS.block), e.contentEditable = !1, e.dataset.placeholder = this.api.i18n.t(this._placeholder), this._data.text && (e.innerHTML = this._data.text), this.readOnly || (e.contentEditable = !0, e.addEventListener("keyup", this.onKeyUp)), e;
}
/**
* Return Tool's view
*
* @returns {HTMLDivElement}
*/
render() {
return this._element = this.drawView(), this._element;
}
/**
* Method that specified how to merge two Text blocks.
* Called by Editor.js by backspace at the beginning of the Block
*
* @param {ParagraphData} data
* @public
*/
merge(e) {
const t = {
text: this.data.text + e.text
};
this.data = t;
}
/**
* Validate Paragraph block data:
* - check for emptiness
*
* @param {ParagraphData} savedData — data received after saving
* @returns {boolean} false if saved data is not correct, otherwise true
* @public
*/
validate(e) {
return !(e.text.trim() === "" && !this._preserveBlank);
}
/**
* Extract Tool's data from the view
*
* @param {HTMLDivElement} toolsContent - Paragraph tools rendered view
* @returns {ParagraphData} - saved data
* @public
*/
save(e) {
return {
text: e.innerHTML
};
}
/**
* On paste callback fired from Editor.
*
* @param {PasteEvent} event - event with pasted data
*/
onPaste(e) {
const t = {
text: e.detail.data.innerHTML
};
this.data = t;
}
/**
* Enable Conversion Toolbar. Paragraph can be converted to/from other tools
*/
static get conversionConfig() {
return {
export: "text",
// to convert Paragraph to other block, use 'text' property of saved data
import: "text"
// to covert other block's exported string to Paragraph, fill 'text' property of tool data
};
}
/**
* Sanitizer rules
*/
static get sanitize() {
return {
text: {
br: !0
}
};
}
/**
* Returns true to notify the core that read-only mode is supported
*
* @returns {boolean}
*/
static get isReadOnlySupported() {
return !0;
}
/**
* Get current Tools`s data
*
* @returns {ParagraphData} Current data
* @private
*/
get data() {
if (this._element !== null) {
const e = this._element.innerHTML;
this._data.text = e;
}
return this._data;
}
/**
* Store data in plugin:
* - at the this._data property
* - at the HTML
*
* @param {ParagraphData} data — data to set
* @private
*/
set data(e) {
this._data = e || {}, this._element !== null && this.hydrate();
}
/**
* Fill tool's view with data
*/
hydrate() {
window.requestAnimationFrame(() => {
this._element.innerHTML = this._data.text || "";
});
}
/**
* Used by Editor paste handling API.
* Provides configuration to handle P tags.
*
* @returns {{tags: string[]}}
*/
static get pasteConfig() {
return {
tags: ["P"]
};
}
/**
* Icon and title for displaying at the Toolbox
*
* @returns {{icon: string, title: string}}
*/
static get toolbox() {
return {
icon: m,
title: "Text"
};
}
}
function y(c, e = 2e3) {
let t;
return (...i) => {
clearTimeout(t), t = setTimeout(() => {
c.apply(null, i);
}, e);
};
}
const p = `<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 4V20M17 12V20M6 20H10M15 20H19M13 7V4H3V7M21 14V12H13V14" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`, g = `<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2.99988V5.99988M12 20.9999V17.9999M4.20577 16.4999L6.80385 14.9999M21 11.9999H18M16.5 19.7941L15 17.196M3 11.9999H6M7.5 4.20565L9 6.80373M7.5 19.7941L9 17.196M19.7942 16.4999L17.1962 14.9999M4.20577 7.49988L6.80385 8.99988" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`, u = class d extends h {
constructor({ api: e, block: t, config: i, data: s }) {
if (super({
api: e,
block: t,
config: i,
data: s
}), this.readOnly = !1, this.onKeyDown = (n) => {
var a;
if (!this.isAcceptKey(n.code))
return;
const r = (a = this._element) == null ? void 0 : a.querySelector("#ai-suggestions");
r != null && r.textContent && (n.preventDefault(), n.stopPropagation(), this.applySuggestion(r));
}, this.onKeyUp = (n) => {
var a, l;
if (this.isDeclineKey(n.code) || n.code === "Backspace") {
(l = (a = this._element) == null ? void 0 : a.querySelector("#ai-suggestions")) == null || l.remove();
return;
}
if (n.code !== "Backspace" && n.code !== "Delete" || !this._element)
return;
const { textContent: r } = this._element;
r === "" && (this._element.innerHTML = "");
}, !i.callback)
throw new Error("Callback function is required!");
this.callback = i.callback, this.acceptKeys = i.acceptKeys ?? ["AltLeft", "AltRight", "Tab"], this.declineKeys = i.declineKeys ?? ["Escape", "Backspace"], this.debounceTimeout = i.debounceTimeout ?? 2e3;
const o = this.acceptKeys.filter((n) => this.declineKeys.includes(n));
o.length && console.warn(`AIText: keys [${o.join(", ")}] are in both acceptKeys and declineKeys`), this.loaderIcon = i.loaderIcon ?? g, d._icon = i.icon ?? p, this.onInput = y((n) => {
var r;
(r = this._element) != null && r.querySelector("#ai-suggestions") || n.inputType === "deleteContentBackward" || n.inputType === "deleteContentForward" || n.inputType === "insertParagraph" || n.inputType === "insertFromPaste" || n.inputType === "insertFromDrop" || !n.target.innerHTML || this.getAICompletion(n.target.innerHTML);
}, this.debounceTimeout);
}
static get toolbox() {
return {
title: "AI TEXT",
icon: d._icon
};
}
static prepare({ config: e }) {
e != null && e.icon && (d._icon = e.icon);
}
getAICompletion(e) {
var i, s;
if (!e)
return;
const t = document.createElement("div");
t.innerHTML = this.loaderIcon, t.id = "ai-suggestions-loader", t.style.display = "inline-flex", t.style.alignItems = "center", t.style.width = "24px", t.style.height = "24px", t.style.paddingLeft = "4px", t.style.color = "lightgray", t.style.position = "absolute", t.animate(
[
{
transform: "rotate(0deg)"
},
{
transform: "rotate(360deg)"
}
],
{
duration: 2e3,
iterations: 1 / 0
}
), (i = this._element) == null || i.appendChild(t), (s = this.callback) == null || s.call(this, e).then((o) => {
var r, a, l;
const n = document.createElement("span");
n.innerHTML = "", n.id = "ai-suggestions", n.style.color = "lightgray", n.innerHTML = o, (r = this._element) == null || r.appendChild(n), (l = (a = this._element) == null ? void 0 : a.querySelector("#ai-suggestions-loader")) == null || l.remove();
}).catch((o) => {
throw new Error(o);
});
}
isAcceptKey(e) {
return this.acceptKeys.includes(e);
}
isDeclineKey(e) {
return this.declineKeys.includes(e);
}
applySuggestion(e) {
var i;
const t = e.textContent;
t && ((i = this._element) == null || i.appendChild(document.createTextNode(t)), e.remove(), this.moveCursorToEnd());
}
moveCursorToEnd() {
if (!this._element)
return;
const e = document.createRange(), t = window.getSelection();
e.selectNodeContents(this._element), e.collapse(!1), t == null || t.removeAllRanges(), t == null || t.addRange(e);
}
drawView() {
const e = document.createElement("DIV");
return e.classList.add(this._CSS.wrapper, this._CSS.block), e.contentEditable = "false", e.dataset.placeholder = this.api.i18n.t(this._placeholder), this._data.text && (e.innerHTML = this._data.text), this.readOnly || (e.contentEditable = "true", e.addEventListener("keydown", this.onKeyDown), e.addEventListener("keyup", this.onKeyUp), e.addEventListener("input", this.onInput)), e;
}
_placeholder(e) {
throw new Error("Method not implemented.");
}
};
u._icon = p;
let x = u;
export {
x as default
};