diff --git a/packages/core/src/base/BasePlugin.ts b/packages/core/src/base/BasePlugin.ts new file mode 100644 index 0000000..4b869bd --- /dev/null +++ b/packages/core/src/base/BasePlugin.ts @@ -0,0 +1,6 @@ +import { Editor } from "@tiptap/core"; + +export abstract class BasePlugin { + abstract register(editor: Editor, option?: T): void +} +export default BasePlugin \ No newline at end of file diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a2e6aa6..a3e9ebb 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,9 +2,11 @@ import { Editor } from "@tiptap/core"; import StarterKit from "@tiptap/starter-kit"; import CharacterCount from "@tiptap/extension-character-count"; import BubbleMenu, { BubbleMenuPlugin } from "@tiptap/extension-bubble-menu"; -import Image from "@tiptap/extension-image"; +// import Image from "@tiptap/extension-image"; +import { Image } from "./plugins/Image/Image"; import { Markdown } from "tiptap-markdown"; -import { getDom } from "./utils.js"; +import { getDom } from "./utils"; +import ImagePlugin from "./plugins/Image"; import "./style/index.scss"; @@ -20,7 +22,8 @@ class Inkeon { const BubbleMenuElement = document.querySelector( ".bubble-menu" ) as HTMLElement; - BubbleMenuElement.querySelector("button").addEventListener("click", () => { + + BubbleMenuElement.querySelector("button")!.addEventListener("click", () => { if (!this.#editor) return; const altInput = BubbleMenuElement.querySelector( ".alt-input" @@ -75,7 +78,9 @@ class Inkeon { }), ], }); - document.querySelector(".aa").addEventListener("click", () => { + // new ImagePlugin().register(this.#editor) + + document.querySelector(".aa")!.addEventListener("click", () => { // console.log(this.#editor.getJSON()); // console.log(this.#editor.getHTML()); console.log(this.#editor.getText()); diff --git a/packages/core/src/plugins/Image/Image.ts b/packages/core/src/plugins/Image/Image.ts new file mode 100644 index 0000000..c8524f4 --- /dev/null +++ b/packages/core/src/plugins/Image/Image.ts @@ -0,0 +1,130 @@ +import { + mergeAttributes, + Node, + nodeInputRule, +} from '@tiptap/core' + +export interface ImageOptions { + /** + * Controls if the image node should be inline or not. + * @default false + * @example true + */ + inline: boolean, + + /** + * Controls if base64 images are allowed. Enable this if you want to allow + * base64 image urls in the `src` attribute. + * @default false + * @example true + */ + allowBase64: boolean, + + /** + * HTML attributes to add to the image element. + * @default {} + * @example { class: 'foo' } + */ + HTMLAttributes: Record, +} + +declare module '@tiptap/core' { + interface Commands { + image: { + /** + * Add an image + * @param options The image attributes + * @example + * editor + * .commands + * .setImage({ src: 'https://tiptap.dev/logo.png', alt: 'tiptap', title: 'tiptap logo' }) + */ + setImage: (options: { src: string, alt?: string, title?: string }) => ReturnType, + } + } +} + +/** + * Matches an image to a ![image](src "title") on input. + */ +export const inputRegex = /(?:^|\s)(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/ + +/** + * This extension allows you to insert images. + * @see https://www.tiptap.dev/api/nodes/image + */ +export const Image = Node.create({ + name: 'image', + + addOptions() { + return { + inline: false, + allowBase64: false, + HTMLAttributes: {}, + } + }, + + inline() { + return this.options.inline + }, + + group() { + return this.options.inline ? 'inline' : 'block' + }, + + draggable: true, + + addAttributes() { + return { + src: { + default: null, + }, + alt: { + default: null, + }, + title: { + default: null, + }, + } + }, + + parseHTML() { + return [ + { + tag: this.options.allowBase64 + ? 'img[src]' + : 'img[src]:not([src^="data:"])', + }, + ] + }, + + renderHTML({ HTMLAttributes }) { + // return ['img', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes,{dataAa: 123})] + return ['div', { "style": "display:inline-block" }, ["img", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { dataAa: 123 })]] + }, + + addCommands() { + return { + setImage: options => ({ commands }) => { + return commands.insertContent({ + type: this.name, + attrs: options, + }) + }, + } + }, + + addInputRules() { + return [ + nodeInputRule({ + find: inputRegex, + type: this.type, + getAttributes: match => { + const [, , alt, src, title] = match + + return { src, alt, title } + }, + }), + ] + }, +}) diff --git a/packages/core/src/plugins/Image/index.ts b/packages/core/src/plugins/Image/index.ts new file mode 100644 index 0000000..cc6d56c --- /dev/null +++ b/packages/core/src/plugins/Image/index.ts @@ -0,0 +1,16 @@ +import { Editor } from "@tiptap/core"; +import { Image } from "./Image"; +import BasePlugin from "../../base/BasePlugin.js"; + +class ImagePlugin extends BasePlugin { + register(editor: Editor) { + editor.extensionManager.extensions.push(Image.configure({ + inline: true, + })) + } +} + +export { + ImagePlugin +} +export default ImagePlugin \ No newline at end of file diff --git a/scripts/build-one.ts b/scripts/build-one.ts index 48fb0bb..2c8b666 100644 --- a/scripts/build-one.ts +++ b/scripts/build-one.ts @@ -12,7 +12,7 @@ export async function buildOne( const pkgInfo = await import( URL.pathToFileURL(path.resolve(rootDir, "package.json")).href ); - const alias = { [pkgInfo.name]: path.resolve(rootDir, "./src") }; + const alias = { [pkgInfo.name]: path.resolve(rootDir, "./src"), ["@"]: path.resolve(rootDir, "./src") }; return build(rootDir, false, { stub: type === "stub", diff --git a/tsconfig.json b/tsconfig.json index f17b88f..2dd44de 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "nodenext", + "module": "ESNext", + "target": "ESNext", + "moduleResolution": "bundler", "baseUrl": ".", "paths": { "@inkeon/*": [