@shenjipo/mention-editor 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.d.mts +20 -5
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +8 -8
package/dist/esm/index.d.mts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Editor } from '@tiptap/core';
|
|
2
2
|
|
|
3
3
|
interface MentionBridge {
|
|
4
|
+
query: string;
|
|
5
|
+
setQuery: (val: string) => void;
|
|
4
6
|
items: MentionItem[];
|
|
5
7
|
selectedIndex: number;
|
|
6
|
-
select: (index: number) => void;
|
|
7
8
|
confirm: (index: number) => void;
|
|
8
9
|
setOptions: (val: Array<MentionItem>) => void;
|
|
9
10
|
}
|
|
@@ -11,21 +12,35 @@ interface MentionItem {
|
|
|
11
12
|
id: string;
|
|
12
13
|
label: string;
|
|
13
14
|
description?: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface MentionStorage {
|
|
18
|
+
active: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ImageInputPayload {
|
|
22
|
+
file: File;
|
|
23
|
+
base64: string;
|
|
24
|
+
width?: number;
|
|
25
|
+
height?: number;
|
|
14
26
|
}
|
|
15
27
|
|
|
16
|
-
interface
|
|
28
|
+
interface MentionEditorOptions {
|
|
17
29
|
element: HTMLElement;
|
|
18
30
|
suggestionDom: HTMLElement;
|
|
19
31
|
bridge: MentionBridge;
|
|
32
|
+
onUpdate: (text: string) => void;
|
|
20
33
|
content?: string;
|
|
21
34
|
onCommand?: (item: MentionItem) => void;
|
|
22
35
|
placeholder?: string;
|
|
36
|
+
onImageInput?: (payload: ImageInputPayload) => void;
|
|
37
|
+
onFileReject?: (file: File) => void;
|
|
23
38
|
}
|
|
24
39
|
interface MentionEditor {
|
|
25
40
|
editor: Editor;
|
|
41
|
+
getHtml: () => string;
|
|
26
42
|
destroy: () => void;
|
|
27
|
-
getJSON: () => any;
|
|
28
43
|
}
|
|
29
|
-
declare function createMentionEditor(options:
|
|
44
|
+
declare function createMentionEditor(options: MentionEditorOptions): MentionEditor;
|
|
30
45
|
|
|
31
|
-
export { type MentionBridge, type MentionEditor, type MentionItem, createMentionEditor };
|
|
46
|
+
export { type MentionBridge, type MentionEditor, type MentionItem, type MentionStorage, createMentionEditor };
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Editor as
|
|
1
|
+
import{Editor as B}from"@tiptap/core";import E from"@tiptap/extension-document";import h from"@tiptap/extension-paragraph";import D from"@tiptap/extension-text";var u=[E,h,D];import P from"@tiptap/extension-mention";function l(e){return()=>{let t=null,r=null,n=[],i=0,m,d=null;function x(){a(),document.body.appendChild(e.suggestionDom),F(e.suggestionDom,t)}let a=()=>{e.bridge.selectedIndex=i};e.bridge.confirm=o=>{g(o)};function g(o){let s=n[o];s&&(s.disabled?e.suggestionDom.remove():(r&&(d.chain().focus().deleteRange(r).run(),r=null),m(s)))}return{onStart(o){d=o.editor,r=o.range,t=o.clientRect(),n=e.bridge.items,m=o.command,i=0,x()},onUpdate(o){n=e.bridge.items,r=o.range},onKeyDown({event:o}){return o.key==="ArrowDown"?(i=(i+1)%n.length,a(),!0):o.key==="ArrowUp"?(i=(i-1+n.length)%n.length,a(),!0):o.key==="Enter"?(g(i),!0):!1},onExit(){e.suggestionDom.remove()}}}}function F(e,t){e.style.position="absolute",e.style.left=`${t.left}px`,e.style.top=`${t.bottom+4}px`}function c(e){return{char:"/",startOfLine:!1,items:({query:t})=>(e.bridge.setQuery(t),e.bridge.items),command:({editor:t,range:r,props:n})=>{t.chain().focus().insertContent({type:"mention",attrs:{id:n.id,label:n.label}}).run(),e.onCommand?.(n)},render:l({suggestionDom:e.suggestionDom,bridge:e.bridge})}}function f(e){return P.configure({HTMLAttributes:{class:"mention","data-type":"mention"},suggestion:c(e),renderHTML({node:t}){return["span",{class:"mention","data-id":t.attrs.id,"data-label":t.attrs.label},t.attrs.label]}})}import H from"@tiptap/extension-placeholder";import{Extension as w}from"@tiptap/core";var p=w.create({name:"customKeyboard",addKeyboardShortcuts(){return{"Shift-Enter":({editor:e})=>e.commands.setHardBreak(),Enter:({editor:e})=>(e.emit("enter"),!1)}}});import v from"@tiptap/extension-hard-break";import{Extension as R}from"@tiptap/core";import{Plugin as T}from"prosemirror-state";function I(e){return e.type.startsWith("image/")}function M(e){return new Promise((t,r)=>{let n=new FileReader;n.onload=()=>t(n.result),n.onerror=r,n.readAsDataURL(e)})}function b(e){return new Promise(t=>{let r=new Image;r.onload=()=>t({width:r.width,height:r.height}),r.src=e})}var y=R.create({name:"imageInput",addOptions(){return{onImageInput:void 0,onFileReject:void 0}},addProseMirrorPlugins(){let e=async t=>{if(!I(t)){this.options.onFileReject?.(t);return}let r=await M(t),n=await b(r);this.options.onImageInput?.({file:t,base64:r,...n})};return[new T({props:{handlePaste:(t,r)=>{let n=r.clipboardData?.items;if(!n)return!1;for(let i of n){if(i.kind!=="file")continue;let m=i.getAsFile();if(m)return e(m),!0}return!1},handleDrop:(t,r)=>{let n=r.dataTransfer?.files;return!n||n.length===0?!1:(e(n[0]),!0)}}})]}});function O(e){let t=new B({element:e.element,extensions:[...u,f({onCommand:e.onCommand,suggestionDom:e.suggestionDom,bridge:e.bridge}),H.configure({placeholder:e.placeholder||"\u8BF7\u8F93\u5165...",emptyEditorClass:"is-editor-empty"}),p,v.configure({keepMarks:!0}),y.configure({onImageInput:e.onImageInput,onFileReject:e.onFileReject})],content:e.content??"",onUpdate:({editor:r})=>{e.onUpdate(r.getText())}});return{editor:t,destroy:()=>t.destroy(),getHtml:()=>t.getHTML()}}export{O as createMentionEditor};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/editor.ts","../../src/schema/base.ts","../../src/mention/index.ts","../../src/utils/dom.ts","../../src/mention/renderer.ts","../../src/mention/suggestion.ts"],"sourcesContent":["import { Editor } from '@tiptap/core'\nimport { baseExtensions } from './schema/base'\nimport { createMentionExtension } from './mention/index'\nimport type { MentionBridge, MentionItem } from './mention/types'\nimport Placeholder from '@tiptap/extension-placeholder'\n\n\nexport interface CreateMentionEditorOptions {\n element: HTMLElement\n suggestionDom: HTMLElement,\n bridge: MentionBridge,\n content?: string\n onCommand?: (item: MentionItem) => void,\n placeholder?: string\n}\n\nexport interface MentionEditor {\n editor: Editor\n destroy: () => void\n getJSON: () => any\n}\n\nexport function createMentionEditor(options: CreateMentionEditorOptions,): MentionEditor {\n const editor = new Editor({\n element: options.element,\n extensions: [\n ...baseExtensions,\n createMentionExtension({\n onCommand: options.onCommand,\n suggestionDom: options.suggestionDom,\n bridge: options.bridge\n }),\n Placeholder.configure({\n placeholder: options.placeholder || '请输入...',\n emptyEditorClass: 'is-editor-empty'\n })\n ],\n content: options.content ?? '',\n })\n\n return {\n editor,\n destroy: () => editor.destroy(),\n getJSON: () => editor.getJSON(),\n }\n}\n","import Document from '@tiptap/extension-document'\nimport Paragraph from '@tiptap/extension-paragraph'\nimport Text from '@tiptap/extension-text'\n\nexport const baseExtensions = [\n Document,\n Paragraph,\n Text,\n]\n","import Mention from '@tiptap/extension-mention'\nimport { createSuggestion } from './suggestion'\nimport type { MentionBridge, MentionItem } from './types'\n\ninterface MentionExtensionOptions {\n onCommand?: (item: MentionItem) => void,\n suggestionDom: HTMLElement,\n bridge: MentionBridge,\n}\n\nexport function createMentionExtension(options: MentionExtensionOptions) {\n return Mention.configure({\n HTMLAttributes: {\n class: 'mention',\n 'data-type': 'mention',\n },\n suggestion: createSuggestion(options),\n renderHTML({ node }) {\n return [\n 'span',\n {\n class: 'mention',\n 'data-id': node.attrs.id,\n 'data-label': node.attrs.label,\n },\n node.attrs.label,\n ]\n }\n })\n}\n","export function createElement<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n props: Partial<HTMLElementTagNameMap[K]> = {},\n): HTMLElementTagNameMap[K] {\n const el = document.createElement(tag)\n Object.assign(el, props)\n return el\n}\n","import type { MentionItem, MentionBridge } from './types'\nimport { createElement } from '../utils/dom'\n\nexport function createMentionRenderer(options: {\n suggestionDom: HTMLElement\n bridge: MentionBridge\n}) {\n return () => {\n let container: HTMLElement | null = null\n let items: MentionItem[] = []\n let selectedIndex = 0\n let command: (item: MentionItem) => void\n\n function render() {\n if (!container) return\n syncBridge()\n container.innerHTML = ''\n container!.appendChild(options.suggestionDom)\n }\n\n const syncBridge = () => {\n options.bridge.items = items\n options.bridge.selectedIndex = selectedIndex\n }\n\n options.bridge.select = (index: number) => {\n selectedIndex = index\n syncBridge()\n }\n\n options.bridge.confirm = (index: number) => {\n command(items[index])\n }\n\n return {\n onStart(props: any) {\n console.log('onStart')\n container = createElement('div', {\n className: 'mention-menu',\n })\n\n document.body.appendChild(container)\n position(container, props.clientRect())\n\n items = props.items\n command = props.command\n selectedIndex = 0\n render()\n },\n\n onUpdate(props: any) {\n console.log('onUpdate')\n items = props.items\n selectedIndex = 0\n render()\n },\n\n onKeyDown({ event }: { event: KeyboardEvent }) {\n if (event.key === 'ArrowDown') {\n console.log('ArrowDown')\n selectedIndex = (selectedIndex + 1) % items.length\n syncBridge()\n return true\n }\n if (event.key === 'ArrowUp') {\n console.log('ArrowUp')\n selectedIndex = (selectedIndex - 1 + items.length) % items.length\n syncBridge()\n return true\n }\n if (event.key === 'Enter') {\n command(items[selectedIndex])\n return true\n }\n return false\n },\n\n onExit() {\n container?.remove()\n container = null\n },\n }\n }\n}\n\nfunction position(el: HTMLElement, rect: DOMRect) {\n el.style.position = 'absolute'\n el.style.left = `${rect.left}px`\n el.style.top = `${rect.bottom + 4}px`\n}\n","import type { Editor } from '@tiptap/core'\nimport type { MentionBridge, MentionItem } from './types'\nimport { createMentionRenderer } from './renderer'\n\ninterface SuggestionOptions {\n onCommand?: (item: MentionItem) => void,\n suggestionDom: HTMLElement,\n bridge: MentionBridge,\n}\n\ninterface CommandProps {\n editor: Editor\n range: { from: number; to: number }\n props: MentionItem\n}\n\nexport function createSuggestion(options: SuggestionOptions) {\n return {\n char: '/',\n startOfLine: false,\n\n items: ({ query }: { query: string }): MentionItem[] => {\n // const list: MentionItem[] = [\n // { id: 'summary', label: '总结' },\n // { id: 'translate', label: '翻译' },\n // { id: 'rewrite', label: '改写' },\n // ]\n\n // if (!query) return list\n return options.bridge.items\n },\n\n command: ({ editor, range, props }: CommandProps) => {\n editor.chain().focus().deleteRange(range).insertContent({\n type: 'mention',\n attrs: {\n id: props.id,\n label: props.label,\n },\n }).run()\n\n options.onCommand?.(props)\n },\n\n render: createMentionRenderer({\n suggestionDom: options.suggestionDom,\n bridge: options.bridge\n }),\n \n }\n}\n"],"mappings":"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","names":["Editor","Document","Paragraph","Text","baseExtensions","Mention","createElement","tag","props","el","createMentionRenderer","options","container","items","selectedIndex","command","render","syncBridge","index","props","createElement","position","event","el","rect","createSuggestion","options","query","editor","range","props","createMentionRenderer","createMentionExtension","options","Mention","createSuggestion","node","Placeholder","createMentionEditor","options","editor","Editor","baseExtensions","createMentionExtension"]}
|
|
1
|
+
{"version":3,"sources":["../../src/editor.ts","../../src/schema/base.ts","../../src/mention/index.ts","../../src/mention/renderer.ts","../../src/mention/suggestion.ts","../../src/extensions/keyboard.ts","../../src/extensions/imageInput.ts","../../src/utils/file.ts"],"sourcesContent":["import { Editor } from '@tiptap/core'\nimport { baseExtensions } from './schema/base'\nimport { createMentionExtension } from './mention/index'\nimport type { MentionBridge, MentionItem } from './mention/types'\nimport Placeholder from '@tiptap/extension-placeholder'\nimport { KeyboardExtension } from './extensions/keyboard'\nimport HardBreak from '@tiptap/extension-hard-break'\nimport { ImageInputPayload } from './utils/file'\nimport { ImageInputExtension } from './extensions/imageInput'\n\nexport interface MentionEditorOptions {\n element: HTMLElement\n suggestionDom: HTMLElement,\n bridge: MentionBridge,\n onUpdate: (text: string) => void\n content?: string\n onCommand?: (item: MentionItem) => void,\n placeholder?: string\n\n onImageInput?: (payload: ImageInputPayload) => void\n onFileReject?: (file: File) => void\n}\n\nexport interface MentionEditor {\n editor: Editor\n getHtml: () => string\n destroy: () => void\n\n}\n\nexport function createMentionEditor(options: MentionEditorOptions): MentionEditor {\n const editor = new Editor({\n element: options.element,\n extensions: [\n ...baseExtensions,\n createMentionExtension({\n onCommand: options.onCommand,\n suggestionDom: options.suggestionDom,\n bridge: options.bridge\n }),\n Placeholder.configure({\n placeholder: options.placeholder || '请输入...',\n emptyEditorClass: 'is-editor-empty'\n }),\n KeyboardExtension,\n HardBreak.configure({\n keepMarks: true, // 保留样式(加粗 / mention)\n }),\n ImageInputExtension.configure({\n onImageInput: options.onImageInput,\n onFileReject: options.onFileReject,\n }),\n ],\n content: options.content ?? '',\n onUpdate: ({ editor }) => {\n options.onUpdate(editor.getText())\n }\n })\n\n return {\n editor,\n destroy: () => editor.destroy(),\n getHtml: () => editor.getHTML(),\n }\n}\n","import Document from '@tiptap/extension-document'\nimport Paragraph from '@tiptap/extension-paragraph'\nimport Text from '@tiptap/extension-text'\n\nexport const baseExtensions = [\n Document,\n Paragraph,\n Text,\n]\n","import Mention from '@tiptap/extension-mention'\nimport { createSuggestion } from './suggestion'\nimport type { MentionBridge, MentionItem } from './types'\n\ninterface MentionExtensionOptions {\n onCommand?: (item: MentionItem) => void,\n suggestionDom: HTMLElement,\n bridge: MentionBridge,\n}\n\nexport function createMentionExtension(options: MentionExtensionOptions) {\n return Mention.configure({\n HTMLAttributes: {\n class: 'mention',\n 'data-type': 'mention',\n },\n suggestion: createSuggestion(options),\n renderHTML({ node }) {\n return [\n 'span',\n {\n class: 'mention',\n 'data-id': node.attrs.id,\n 'data-label': node.attrs.label,\n },\n node.attrs.label,\n ]\n },\n })\n}\n","import type { MentionItem, MentionBridge } from './types'\nimport { Editor } from '@tiptap/core'\n\nexport function createMentionRenderer(options: {\n suggestionDom: HTMLElement\n bridge: MentionBridge\n}) {\n return () => {\n let clientRect: DOMRect | null = null\n let range: { from: number; to: number } | null = null\n let mentionItems: MentionItem[] = []\n let selectedIndex = 0\n let command: (item: MentionItem) => void\n let editor: Editor | null = null\n\n function render() {\n syncBridge()\n document.body.appendChild(options.suggestionDom)\n position(options.suggestionDom, clientRect)\n }\n\n const syncBridge = () => {\n options.bridge.selectedIndex = selectedIndex\n }\n\n options.bridge.confirm = (index: number) => {\n confirmItem(index)\n }\n\n function confirmItem(index: number) {\n const item = mentionItems[index]\n if (!item) return\n\n if (item.disabled) {\n // editor?.chain().focus().insertContent(' ').run()\n options.suggestionDom.remove()\n } else {\n if (range) {\n editor.chain().focus().deleteRange(range).run()\n range = null\n }\n command(item)\n }\n }\n\n\n return {\n onStart(props: any) {\n editor = props.editor\n range = props.range\n clientRect = props.clientRect()\n mentionItems = options.bridge.items\n command = props.command\n selectedIndex = 0\n render()\n },\n\n onUpdate(props: any) {\n mentionItems = options.bridge.items\n range = props.range\n\n },\n\n onKeyDown({ event }: { event: KeyboardEvent }) {\n if (event.key === 'ArrowDown') {\n selectedIndex = (selectedIndex + 1) % mentionItems.length\n syncBridge()\n return true\n }\n if (event.key === 'ArrowUp') {\n selectedIndex = (selectedIndex - 1 + mentionItems.length) % mentionItems.length\n syncBridge()\n return true\n }\n if (event.key === 'Enter') {\n\n confirmItem(selectedIndex)\n return true\n }\n return false\n },\n\n onExit() {\n options.suggestionDom.remove()\n },\n }\n }\n}\n\nfunction position(el: HTMLElement, rect: DOMRect) {\n el.style.position = 'absolute'\n el.style.left = `${rect.left}px`\n el.style.top = `${rect.bottom + 4}px`\n}\n","import type { Editor } from '@tiptap/core'\nimport type { MentionBridge, MentionItem } from './types'\nimport { createMentionRenderer } from './renderer'\n\ninterface SuggestionOptions {\n onCommand?: (item: MentionItem) => void,\n suggestionDom: HTMLElement,\n bridge: MentionBridge,\n}\n\ninterface CommandProps {\n editor: Editor\n range: { from: number; to: number }\n props: MentionItem\n}\n\nexport function createSuggestion(options: SuggestionOptions) {\n return {\n char: '/',\n startOfLine: false,\n\n items: ({ query }: { query: string }): MentionItem[] => {\n options.bridge.setQuery(query)\n return options.bridge.items\n },\n\n command: ({ editor, range, props }: CommandProps) => {\n \n editor.chain().focus().insertContent({\n type: 'mention',\n attrs: {\n id: props.id,\n label: props.label,\n },\n }).run()\n \n options.onCommand?.(props)\n },\n\n render: createMentionRenderer({\n suggestionDom: options.suggestionDom,\n bridge: options.bridge\n }),\n\n }\n}\n","// extensions/keyboard.ts\nimport { Extension } from '@tiptap/core'\n\nexport const KeyboardExtension = Extension.create({\n name: 'customKeyboard',\n\n addKeyboardShortcuts() {\n return {\n // Shift + Enter = 换行\n 'Shift-Enter': ({ editor }) => {\n return editor.commands.setHardBreak()\n },\n // Enter = 只触发事件,不换行\n Enter: ({ editor }) => {\n editor.emit('enter')\n return false // 阻止默认换行\n },\n }\n },\n})\n","import { Extension } from '@tiptap/core'\nimport { Plugin } from 'prosemirror-state'\nimport {\n isImageFile,\n fileToBase64,\n getImageSize,\n ImageInputPayload,\n} from '../utils/file'\n\nexport interface ImageInputOptions {\n onImageInput?: (payload: ImageInputPayload) => void\n onFileReject?: (file: File) => void\n}\n\nexport const ImageInputExtension = Extension.create<ImageInputOptions>({\n name: 'imageInput',\n\n addOptions() {\n return {\n onImageInput: undefined,\n onFileReject: undefined,\n }\n },\n\n addProseMirrorPlugins() {\n const handleFile = async (file: File) => {\n if (!isImageFile(file)) {\n this.options.onFileReject?.(file)\n return\n }\n\n const base64 = await fileToBase64(file)\n const size = await getImageSize(base64)\n\n this.options.onImageInput?.({\n file,\n base64,\n ...size,\n })\n }\n\n return [\n new Plugin({\n props: {\n handlePaste: (_, event) => {\n const items = event.clipboardData?.items\n if (!items) return false\n\n for (const item of items) {\n if (item.kind !== 'file') continue\n const file = item.getAsFile()\n if (!file) continue\n\n handleFile(file)\n return true\n }\n\n return false\n },\n\n handleDrop: (_, event) => {\n const files = event.dataTransfer?.files\n if (!files || files.length === 0) return false\n\n handleFile(files[0])\n return true\n },\n },\n }),\n ]\n },\n})\n","export interface ImageInputPayload {\n file: File\n base64: string\n width?: number\n height?: number\n}\n\nexport function isImageFile(file: File) {\n return file.type.startsWith('image/')\n}\n\nexport function fileToBase64(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(reader.result as string)\n reader.onerror = reject\n reader.readAsDataURL(file)\n })\n}\n\nexport function getImageSize(base64: string): Promise<{ width: number; height: number }> {\n return new Promise(resolve => {\n const img = new Image()\n img.onload = () => resolve({ width: img.width, height: img.height })\n img.src = base64\n })\n}\n"],"mappings":"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","names":["Editor","Document","Paragraph","Text","baseExtensions","Mention","createMentionRenderer","options","clientRect","range","mentionItems","selectedIndex","command","editor","render","syncBridge","position","index","confirmItem","item","props","event","el","rect","createSuggestion","options","query","editor","range","props","createMentionRenderer","createMentionExtension","options","Mention","createSuggestion","node","Placeholder","Extension","KeyboardExtension","editor","HardBreak","Extension","Plugin","isImageFile","file","fileToBase64","resolve","reject","reader","getImageSize","base64","img","ImageInputExtension","Extension","handleFile","file","isImageFile","base64","fileToBase64","size","getImageSize","Plugin","_","event","items","item","files","createMentionEditor","options","editor","Editor","baseExtensions","createMentionExtension","Placeholder","KeyboardExtension","HardBreak","ImageInputExtension"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shenjipo/mention-editor",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"module": "dist/esm/index.mjs",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -14,14 +14,9 @@
|
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build:watch": "tsup --watch",
|
|
19
|
-
"build": "tsup"
|
|
20
|
-
},
|
|
21
17
|
"keywords": [],
|
|
22
18
|
"author": "",
|
|
23
19
|
"license": "ISC",
|
|
24
|
-
"packageManager": "pnpm@10.10.0",
|
|
25
20
|
"dependencies": {
|
|
26
21
|
"@tiptap/core": "2.7.1",
|
|
27
22
|
"@tiptap/extension-document": "2.7.1",
|
|
@@ -29,11 +24,16 @@
|
|
|
29
24
|
"@tiptap/extension-paragraph": "2.7.1",
|
|
30
25
|
"@tiptap/extension-mention": "2.7.1",
|
|
31
26
|
"@tiptap/extension-placeholder": "2.7.1",
|
|
27
|
+
"@tiptap/extension-hard-break": "2.7.1",
|
|
32
28
|
"prosemirror-state": "^1.4.3",
|
|
33
29
|
"prosemirror-model": "^1.21.0",
|
|
34
30
|
"prosemirror-view": "^1.33.7"
|
|
35
31
|
},
|
|
36
32
|
"main": "index.js",
|
|
37
33
|
"devDependencies": {},
|
|
38
|
-
"description": ""
|
|
39
|
-
|
|
34
|
+
"description": "",
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build:watch": "tsup --watch",
|
|
37
|
+
"build": "tsup"
|
|
38
|
+
}
|
|
39
|
+
}
|