improve new extensions

This commit is contained in:
Philipp Kühn 2020-11-16 09:43:17 +01:00
parent 034ee139a3
commit c87f49c1fe
50 changed files with 296 additions and 377 deletions

View File

@ -27,9 +27,9 @@ export default {
mounted() { mounted() {
this.html = generateHTML(this.json, [ this.html = generateHTML(this.json, [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
]) ])
}, },
} }

View File

@ -135,11 +135,11 @@ export default {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
...defaultExtensions(), ...defaultExtensions(),
Collaboration({ Collaboration.set({
provider: this.provider, provider: this.provider,
type: this.type, type: this.type,
}), }),
CollaborationCursor({ CollaborationCursor.set({
provider: this.provider, provider: this.provider,
name: this.name, name: this.name,
color: this.color, color: this.color,

View File

@ -61,16 +61,16 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Heading({ Heading.set({
level: [1, 2, 3], level: [1, 2, 3],
}), }),
Bold(), Bold,
Italic(), Italic,
TextAlign(), TextAlign,
HardBreak(), HardBreak,
], ],
content: ` content: `
<h3>Girls Just Want to Have Fun (Cyndi Lauper)</h2> <h3>Girls Just Want to Have Fun (Cyndi Lauper)</h2>

View File

@ -32,10 +32,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Link(), Link,
], ],
content: ` content: `
<p> <p>

View File

@ -37,7 +37,7 @@ export default {
`, `,
extensions: [ extensions: [
...defaultExtensions(), ...defaultExtensions(),
Highlight(), Highlight,
], ],
}) })
}, },

View File

@ -22,9 +22,9 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
], ],
content: ` content: `
<p> <p>

View File

@ -35,11 +35,11 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
CustomDocument(), CustomDocument,
Paragraph(), Paragraph,
Text(), Text,
TaskList(), TaskList,
CustomTaskItem(), CustomTaskItem,
], ],
content: ` content: `
<ul data-type="taskList"> <ul data-type="taskList">

View File

@ -26,10 +26,10 @@ export default {
this.editor = new Editor({ this.editor = new Editor({
content: '<p>Im running tiptap with Vue.js. This demo is interactive, try to edit the text.</p>', content: '<p>Im running tiptap with Vue.js. This demo is interactive, try to edit the text.</p>',
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Bold(), Bold,
], ],
}) })
}, },

View File

@ -37,10 +37,10 @@ export default {
// <p>Example Text</p> // <p>Example Text</p>
// `, // `,
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Collaboration({ Collaboration.set({
provider: this.provider, provider: this.provider,
type: this.type, type: this.type,
}), }),

View File

@ -38,14 +38,14 @@ export default {
// <p>Example Text</p> // <p>Example Text</p>
// `, // `,
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Collaboration({ Collaboration.set({
provider: this.provider, provider: this.provider,
type: this.type, type: this.type,
}), }),
CollaborationCursor({ CollaborationCursor.set({
provider: this.provider, provider: this.provider,
name: 'Cyndi Lauper', name: 'Cyndi Lauper',
color: '#f783ac', color: '#f783ac',

View File

@ -26,11 +26,11 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Image(), Image,
Dropcursor(), Dropcursor,
], ],
content: ` content: `
<p>Try to drag around the image. While you drag, the editor should show a decoration under your cursor. The so called dropcursor.</p> <p>Try to drag around the image. While you drag, the editor should show a decoration under your cursor. The so called dropcursor.</p>

View File

@ -28,16 +28,16 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Focus({ Focus.set({
className: 'has-focus', className: 'has-focus',
nested: true, nested: true,
}), }),
Code(), Code,
BulletList(), BulletList,
ListItem(), ListItem,
], ],
autoFocus: true, autoFocus: true,
content: ` content: `

View File

@ -46,11 +46,11 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
TextStyle(), TextStyle,
FontFamily(), FontFamily,
], ],
content: ` content: `
<p><span style="font-family: Inter">Did you know that Inter is a really nice font for interfaces?</span></p> <p><span style="font-family: Inter">Did you know that Inter is a really nice font for interfaces?</span></p>

View File

@ -26,11 +26,11 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Image(), Image,
Gapcursor(), Gapcursor,
], ],
content: ` content: `
<p>Try to set the cursor behind the image with your arrow keys! You should see big blinking cursor right from the image. This is the gapcursor.</p> <p>Try to set the cursor behind the image with your arrow keys! You should see big blinking cursor right from the image. This is the gapcursor.</p>

View File

@ -33,10 +33,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
History(), History,
], ],
content: ` content: `
<p> <p>

View File

@ -42,11 +42,11 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Heading(), Heading,
TextAlign(), TextAlign,
], ],
content: ` content: `
<h2>Heading</h2> <h2>Heading</h2>

View File

@ -25,10 +25,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Typography(), Typography,
], ],
content: ` content: `
<p>I have been suffering from Typomania all my life, a sickness that is incurable but not lethal.</p> <p>I have been suffering from Typomania all my life, a sickness that is incurable but not lethal.</p>

View File

@ -55,15 +55,15 @@ export default {
this.editor = new Editor({ this.editor = new Editor({
content: '<h2>Hey there!</h2><p>This editor is based on Prosemirror, fully extendable and headless. You can easily add custom nodes as Vue components.</p>', content: '<h2>Hey there!</h2><p>This editor is based on Prosemirror, fully extendable and headless. You can easily add custom nodes as Vue components.</p>',
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
CodeBlock(), CodeBlock,
History(), History,
Bold(), Bold,
Italic(), Italic,
Code(), Code,
Heading(), Heading,
], ],
}) })
}, },

View File

@ -30,10 +30,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Bold(), Bold,
], ],
content: ` content: `
<p>This isnt bold.</p> <p>This isnt bold.</p>

View File

@ -30,10 +30,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Code(), Code,
], ],
content: ` content: `
<p>This isnt code.</p> <p>This isnt code.</p>

View File

@ -61,10 +61,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Highlight(), Highlight,
], ],
content: ` content: `
<p>This isnt highlighted.</s></p> <p>This isnt highlighted.</s></p>

View File

@ -30,10 +30,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Italic(), Italic,
], ],
content: ` content: `
<p>This isnt italic.</p> <p>This isnt italic.</p>

View File

@ -32,10 +32,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Link(), Link,
], ],
content: ` content: `
<p> <p>

View File

@ -30,10 +30,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Strike(), Strike,
], ],
content: ` content: `
<p>This isnt striked through.</s></p> <p>This isnt striked through.</s></p>

View File

@ -26,10 +26,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
TextStyle(), TextStyle,
], ],
content: ` content: `
<p><span>This has a &lt;span&gt; tag without a style attribute, so its thrown away.</span></p> <p><span>This has a &lt;span&gt; tag without a style attribute, so its thrown away.</span></p>

View File

@ -30,10 +30,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Underline(), Underline,
], ],
content: ` content: `
<p>There is no underline here.</p> <p>There is no underline here.</p>

View File

@ -30,10 +30,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Blockquote(), Blockquote,
], ],
content: ` content: `
<blockquote> <blockquote>

View File

@ -31,11 +31,11 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
BulletList(), BulletList,
ListItem(), ListItem,
], ],
content: ` content: `
<ul> <ul>

View File

@ -30,10 +30,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
CodeBlock(), CodeBlock,
], ],
content: ` content: `
<p> <p>

View File

@ -25,9 +25,9 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
], ],
content: ` content: `
<p>The Document extension is required. Though, you can write your own implementation, e. g. to give it custom name.</p> <p>The Document extension is required. Though, you can write your own implementation, e. g. to give it custom name.</p>

View File

@ -30,10 +30,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
HardBreak(), HardBreak,
], ],
content: ` content: `
<p> <p>

View File

@ -36,10 +36,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Heading({ Heading.set({
levels: [1, 2, 3], levels: [1, 2, 3],
}), }),
], ],

View File

@ -30,10 +30,10 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
HorizontalRule(), HorizontalRule,
], ],
content: ` content: `
<p>This is a paragraph.</p> <p>This is a paragraph.</p>

View File

@ -38,11 +38,11 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
Image(), Image,
Dropcursor(), Dropcursor,
], ],
content: ` content: `
<p>This is a basic example of implementing images. Drag to re-order.</p> <p>This is a basic example of implementing images. Drag to re-order.</p>

View File

@ -35,12 +35,12 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
BulletList(), BulletList,
OrderedList(), OrderedList,
ListItem(), ListItem,
], ],
content: ` content: `
<p> <p>

View File

@ -31,11 +31,11 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
OrderedList(), OrderedList,
ListItem(), ListItem,
], ],
content: ` content: `
<ol> <ol>

View File

@ -25,9 +25,9 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
], ],
content: ` content: `
<p>The Paragraph extension is not required, but its very likely you want to use it. Its needed to write paragraphs of text. 🤓</p> <p>The Paragraph extension is not required, but its very likely you want to use it. Its needed to write paragraphs of text. 🤓</p>

View File

@ -27,11 +27,11 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
TaskList(), TaskList,
TaskItem(), TaskItem,
], ],
content: ` content: `
<ul data-type="task_list"> <ul data-type="task_list">

View File

@ -31,11 +31,11 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
TaskList(), TaskList,
TaskItem(), TaskItem,
], ],
content: ` content: `
<ul data-type="task_list"> <ul data-type="task_list">

View File

@ -25,9 +25,9 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
], ],
content: ` content: `
<p>The Text extension is required, at least if you want to have text in your text editor and thats very likely.</p> <p>The Text extension is required, at least if you want to have text in your text editor and thats very likely.</p>

View File

@ -14,6 +14,9 @@ import createStyleTag from './utils/createStyleTag'
import CommandManager from './CommandManager' import CommandManager from './CommandManager'
import ExtensionManager from './ExtensionManager' import ExtensionManager from './ExtensionManager'
import EventEmitter from './EventEmitter' import EventEmitter from './EventEmitter'
import { Extension } from './Extension'
import { NodeExtension } from './NodeExtension'
import { MarkExtension } from './MarkExtension'
import { Extensions, UnionToIntersection, PickValue } from './types' import { Extensions, UnionToIntersection, PickValue } from './types'
import * as extensions from './extensions' import * as extensions from './extensions'
import style from './style' import style from './style'
@ -37,7 +40,62 @@ export interface CommandsSpec {
export interface AllExtensions {} export interface AllExtensions {}
export type AllCommands = UnionToIntersection<ReturnType<PickValue<ReturnType<AllExtensions[keyof AllExtensions]>, 'addCommands'>>> // type names = AllExtensions[keyof AllExtensions]
// type onlyExtensions = AllExtensions[keyof AllExtensions] extends Extension ? '1' : '0'
// type onlyExtensions = AllExtensions[keyof AllExtensions extends Extension ? '1' : '0']
// export type OnlyExtensions = {
// [Item in keyof AllExtensions]: AllExtensions[Item] extends Extension
// ? AllExtensions[Item]
// : never
// }
// type ExtractCat<A> = A extends Extension ? A : never
// export type OnlyExtensions = ExtractCat<Item in keyof AllExtensions>
// type ExtractCommands<G> = G extends Extension<any, infer S> ? S : never
// type Test = UnionToIntersection<ExtractCommands<OnlyExtensions[keyof OnlyExtensions]>>
// type SubType<Base, Condition> = Pick<Base, {
// [Key in keyof Base]: Base[Key] extends Condition ? Key : never
// }[keyof Base]>;
// type OnlyExtensions = SubType<AllExtensions, Extension>
// type ExtractCommands<G> = G extends Extension<any, infer S> ? S : never
// type Test = ExtractCommands<OnlyExtensions[keyof OnlyExtensions]>
// type ExtractCommands<G> = G extends Extension<any, infer S>
// ? never
// : G extends NodeExtension<any, infer T>
// ? never
// : G extends MarkExtension<any, infer U>
// ? U
// : never
// export type Bla = {
// [Item in keyof AllExtensions]: AllExtensions[Item] extends Extension<any, infer S>
// ? S
// : AllExtensions[Item] extends NodeExtension<any, infer T>
// ? T
// : AllExtensions[Item] extends MarkExtension<any, infer U>
// ? U
// : never
// }
export type Bla = {
[Item in keyof AllExtensions]: AllExtensions[Item] extends Extension<any, infer ExtensionCommands>
? ExtensionCommands
: AllExtensions[Item] extends NodeExtension<any, infer NodeExtensionCommands>
? NodeExtensionCommands
: AllExtensions[Item] extends MarkExtension<any, infer MarkExtensionCommands>
? MarkExtensionCommands
: never
}
type ValuesOf<T> = T[keyof T];
type KeysWithTypeOf<T, Type> = ({[P in keyof T]: T[P] extends Type ? P : never })[keyof T]
type AllCommands = UnionToIntersection<ValuesOf<Pick<Bla, KeysWithTypeOf<Bla, {}>>>>
export type SingleCommands = { export type SingleCommands = {
[Item in keyof AllCommands]: AllCommands[Item] extends (...args: any[]) => any [Item in keyof AllCommands]: AllCommands[Item] extends (...args: any[]) => any
@ -239,7 +297,7 @@ export class Editor extends EventEmitter {
* Creates an extension manager. * Creates an extension manager.
*/ */
private createExtensionManager() { private createExtensionManager() {
const coreExtensions = Object.entries(extensions).map(([, extension]) => extension()) const coreExtensions = Object.entries(extensions).map(([, extension]) => extension)
const allExtensions = [...this.options.extensions, ...coreExtensions] const allExtensions = [...this.options.extensions, ...coreExtensions]
this.extensionManager = new ExtensionManager(allExtensions, this.proxy) this.extensionManager = new ExtensionManager(allExtensions, this.proxy)

View File

@ -63,55 +63,6 @@ export interface ExtensionSpec<Options = any, Commands = {}> {
}) => Plugin[], }) => Plugin[],
} }
// /**
// * Extension interface for internal usage
// */
// export type Extension = Required<Omit<ExtensionSpec, 'defaultOptions'> & {
// type: string,
// options: {
// [key: string]: any
// },
// }>
// /**
// * Default extension
// */
// export const defaultExtension: Extension = {
// name: 'extension',
// type: 'extension',
// options: {},
// addGlobalAttributes: () => [],
// addCommands: () => ({}),
// addKeyboardShortcuts: () => ({}),
// addInputRules: () => [],
// addPasteRules: () => [],
// addProseMirrorPlugins: () => [],
// }
// export function createExtension<Options extends {}, Commands extends {}>(config: ExtensionSpec<Options, Commands>) {
// const extend = <ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<ExtensionSpec<ExtendedOptions, ExtendedCommands>>) => {
// return createExtension({
// ...config,
// ...extendedConfig,
// } as ExtensionSpec<ExtendedOptions, ExtendedCommands>)
// }
// const setOptions = (options?: Partial<Options>) => {
// const { defaultOptions, ...rest } = config
// return {
// ...defaultExtension,
// ...rest,
// options: {
// ...defaultOptions,
// ...options,
// } as Options,
// }
// }
// return Object.assign(setOptions, { config, extend })
// }
export class Extension<Options = any, Commands = any> { export class Extension<Options = any, Commands = any> {
config: Required<ExtensionSpec> = { config: Required<ExtensionSpec> = {
name: 'extension', name: 'extension',
@ -144,6 +95,8 @@ export class Extension<Options = any, Commands = any> {
...this.config.defaultOptions, ...this.config.defaultOptions,
...options, ...options,
} }
return this
} }
extend<ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<ExtensionSpec<ExtendedOptions, ExtendedCommands>>) { extend<ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<ExtensionSpec<ExtendedOptions, ExtendedCommands>>) {

View File

@ -28,10 +28,10 @@ export default class ExtensionManager {
const context = { const context = {
options: extension.options, options: extension.options,
editor: this.editor, editor: this.editor,
type: getSchemaTypeByName(extension.name, this.schema), type: getSchemaTypeByName(extension.config.name, this.schema),
} }
const commands = extension.addCommands.bind(context)() const commands = extension.config.addCommands.bind(context)()
editor.registerCommands(commands) editor.registerCommands(commands)
}) })
@ -43,10 +43,10 @@ export default class ExtensionManager {
const context = { const context = {
options: extension.options, options: extension.options,
editor: this.editor, editor: this.editor,
type: getSchemaTypeByName(extension.name, this.schema), type: getSchemaTypeByName(extension.config.name, this.schema),
} }
return extension.addProseMirrorPlugins.bind(context)() return extension.config.addProseMirrorPlugins.bind(context)()
}) })
.flat() .flat()
@ -64,10 +64,10 @@ export default class ExtensionManager {
const context = { const context = {
options: extension.options, options: extension.options,
editor: this.editor, editor: this.editor,
type: getSchemaTypeByName(extension.name, this.schema), type: getSchemaTypeByName(extension.config.name, this.schema),
} }
return extension.addInputRules.bind(context)() return extension.config.addInputRules.bind(context)()
}) })
.flat() .flat()
} }
@ -78,10 +78,10 @@ export default class ExtensionManager {
const context = { const context = {
options: extension.options, options: extension.options,
editor: this.editor, editor: this.editor,
type: getSchemaTypeByName(extension.name, this.schema), type: getSchemaTypeByName(extension.config.name, this.schema),
} }
return extension.addPasteRules.bind(context)() return extension.config.addPasteRules.bind(context)()
}) })
.flat() .flat()
} }
@ -91,10 +91,10 @@ export default class ExtensionManager {
const context = { const context = {
options: extension.options, options: extension.options,
editor: this.editor, editor: this.editor,
type: getSchemaTypeByName(extension.name, this.schema), type: getSchemaTypeByName(extension.config.name, this.schema),
} }
return keymap(extension.addKeyboardShortcuts.bind(context)()) return keymap(extension.config.addKeyboardShortcuts.bind(context)())
}) })
} }
@ -104,17 +104,17 @@ export default class ExtensionManager {
const allAttributes = getAttributesFromExtensions(this.extensions) const allAttributes = getAttributesFromExtensions(this.extensions)
return Object.fromEntries(nodeExtensions return Object.fromEntries(nodeExtensions
.filter(extension => !!extension.addNodeView) .filter(extension => !!extension.config.addNodeView)
.map(extension => { .map(extension => {
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name) const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.config.name)
const context = { const context = {
options: extension.options, options: extension.options,
editor, editor,
type: getSchemaTypeByName(extension.name, this.schema), type: getSchemaTypeByName(extension.config.name, this.schema),
} }
// @ts-ignore // @ts-ignore
const renderer = extension.addNodeView?.bind(context)?.() as NodeViewRenderer const renderer = extension.config.addNodeView?.bind(context)?.() as NodeViewRenderer
const nodeview = ( const nodeview = (
node: ProsemirrorNode, node: ProsemirrorNode,
@ -133,7 +133,7 @@ export default class ExtensionManager {
}) })
} }
return [extension.name, nodeview] return [extension.config.name, nodeview]
})) }))
} }

View File

@ -106,51 +106,7 @@ export interface MarkExtensionSpec<Options = any, Commands = {}> extends Overwri
}) => Plugin[], }) => Plugin[],
}> {} }> {}
// export type MarkExtension = Required<Omit<MarkExtensionSpec, 'defaultOptions'> & { export class MarkExtension<Options = any, Commands = {}> {
// type: string,
// options: {
// [key: string]: any
// },
// }>
// const defaultMark: MarkExtension = {
// ...defaultExtension,
// type: 'mark',
// name: 'mark',
// inclusive: null,
// excludes: null,
// group: null,
// spanning: null,
// parseHTML: () => null,
// renderHTML: null,
// addAttributes: () => ({}),
// }
// export function createMark<Options extends {}, Commands extends {}>(config: MarkExtensionSpec<Options, Commands>) {
// const extend = <ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<MarkExtensionSpec<ExtendedOptions, ExtendedCommands>>) => {
// return createMark({
// ...config,
// ...extendedConfig,
// } as MarkExtensionSpec<ExtendedOptions, ExtendedCommands>)
// }
// const setOptions = (options?: Partial<Options>) => {
// const { defaultOptions, ...rest } = config
// return {
// ...defaultMark,
// ...rest,
// options: {
// ...defaultOptions,
// ...options,
// } as Options,
// }
// }
// return Object.assign(setOptions, { config, extend })
// }
export class MarkExtension<Options = any, Commands = any> {
config: Required<MarkExtensionSpec> = { config: Required<MarkExtensionSpec> = {
name: 'mark', name: 'mark',
defaultOptions: {}, defaultOptions: {},
@ -189,6 +145,8 @@ export class MarkExtension<Options = any, Commands = any> {
...this.config.defaultOptions, ...this.config.defaultOptions,
...options, ...options,
} }
return this
} }
extend<ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<MarkExtensionSpec<ExtendedOptions, ExtendedCommands>>) { extend<ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<MarkExtensionSpec<ExtendedOptions, ExtendedCommands>>) {

View File

@ -150,59 +150,7 @@ export interface NodeExtensionSpec<Options = any, Commands = {}> extends Overwri
}) => NodeViewRenderer) | null, }) => NodeViewRenderer) | null,
}> {} }> {}
// export type NodeExtension = Required<Omit<NodeExtensionSpec, 'defaultOptions'> & { export class NodeExtension<Options = any, Commands = {}> {
// type: string,
// options: {
// [key: string]: any
// },
// }>
// const defaultNode: NodeExtension = {
// ...defaultExtension,
// type: 'node',
// name: 'node',
// topNode: false,
// content: null,
// marks: null,
// group: null,
// inline: null,
// atom: null,
// selectable: null,
// draggable: null,
// code: null,
// defining: null,
// isolating: null,
// parseHTML: () => null,
// renderHTML: null,
// addAttributes: () => ({}),
// addNodeView: null,
// }
// export function createNode<Options extends {}, Commands extends {}>(config: NodeExtensionSpec<Options, Commands>) {
// const extend = <ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<NodeExtensionSpec<ExtendedOptions, ExtendedCommands>>) => {
// return createNode({
// ...config,
// ...extendedConfig,
// } as NodeExtensionSpec<ExtendedOptions, ExtendedCommands>)
// }
// const setOptions = (options?: Partial<Options>) => {
// const { defaultOptions, ...rest } = config
// return {
// ...defaultNode,
// ...rest,
// options: {
// ...defaultOptions,
// ...options,
// } as Options,
// }
// }
// return Object.assign(setOptions, { config, extend })
// }
export class NodeExtension<Options = any, Commands = any> {
config: Required<NodeExtensionSpec> = { config: Required<NodeExtensionSpec> = {
name: 'node', name: 'node',
defaultOptions: {}, defaultOptions: {},
@ -249,6 +197,8 @@ export class NodeExtension<Options = any, Commands = any> {
...this.config.defaultOptions, ...this.config.defaultOptions,
...options, ...options,
} }
return this
} }
extend<ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<NodeExtensionSpec<ExtendedOptions, ExtendedCommands>>) { extend<ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<NodeExtensionSpec<ExtendedOptions, ExtendedCommands>>) {

View File

@ -27,7 +27,7 @@ export default function getAttributesFromExtensions(extensions: Extensions) {
options: extension.options, options: extension.options,
} }
const globalAttributes = extension.addGlobalAttributes.bind(context)() as GlobalAttributes const globalAttributes = extension.config.addGlobalAttributes.bind(context)() as GlobalAttributes
globalAttributes.forEach(globalAttribute => { globalAttributes.forEach(globalAttribute => {
globalAttribute.types.forEach(type => { globalAttribute.types.forEach(type => {
@ -52,13 +52,13 @@ export default function getAttributesFromExtensions(extensions: Extensions) {
options: extension.options, options: extension.options,
} }
const attributes = extension.addAttributes.bind(context)() as Attributes const attributes = extension.config.addAttributes.bind(context)() as Attributes
Object Object
.entries(attributes) .entries(attributes)
.forEach(([name, attribute]) => { .forEach(([name, attribute]) => {
extensionAttributes.push({ extensionAttributes.push({
type: extension.name, type: extension.config.name,
name, name,
attribute: { attribute: {
...defaultAttribute, ...defaultAttribute,

View File

@ -21,35 +21,35 @@ function cleanUpSchemaItem<T>(data: T) {
export default function getSchema(extensions: Extensions): Schema { export default function getSchema(extensions: Extensions): Schema {
const allAttributes = getAttributesFromExtensions(extensions) const allAttributes = getAttributesFromExtensions(extensions)
const { nodeExtensions, markExtensions } = splitExtensions(extensions) const { nodeExtensions, markExtensions } = splitExtensions(extensions)
const topNode = nodeExtensions.find(extension => extension.topNode)?.name const topNode = nodeExtensions.find(extension => extension.config.topNode)?.config.name
const nodes = Object.fromEntries(nodeExtensions.map(extension => { const nodes = Object.fromEntries(nodeExtensions.map(extension => {
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name) const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.config.name)
const context = { options: extension.options } const context = { options: extension.options }
const schema: NodeSpec = cleanUpSchemaItem({ const schema: NodeSpec = cleanUpSchemaItem({
content: callOrReturn(extension.content, context), content: callOrReturn(extension.config.content, context),
marks: callOrReturn(extension.marks, context), marks: callOrReturn(extension.config.marks, context),
group: callOrReturn(extension.group, context), group: callOrReturn(extension.config.group, context),
inline: callOrReturn(extension.inline, context), inline: callOrReturn(extension.config.inline, context),
atom: callOrReturn(extension.atom, context), atom: callOrReturn(extension.config.atom, context),
selectable: callOrReturn(extension.selectable, context), selectable: callOrReturn(extension.config.selectable, context),
draggable: callOrReturn(extension.draggable, context), draggable: callOrReturn(extension.config.draggable, context),
code: callOrReturn(extension.code, context), code: callOrReturn(extension.config.code, context),
defining: callOrReturn(extension.defining, context), defining: callOrReturn(extension.config.defining, context),
isolating: callOrReturn(extension.isolating, context), isolating: callOrReturn(extension.config.isolating, context),
attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => { attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => {
return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }] return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }]
})), })),
}) })
if (extension.parseHTML) { if (extension.config.parseHTML) {
schema.parseDOM = extension.parseHTML schema.parseDOM = extension.config.parseHTML
.bind(context)() .bind(context)()
?.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes)) ?.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes))
} }
if (extension.renderHTML) { if (extension.config.renderHTML) {
schema.toDOM = node => (extension.renderHTML as Function)?.bind(context)({ schema.toDOM = node => (extension.config.renderHTML as Function)?.bind(context)({
node, node,
HTMLAttributes: mergeAttributes( HTMLAttributes: mergeAttributes(
extension.options.HTMLAttributes, extension.options.HTMLAttributes,
@ -58,30 +58,30 @@ export default function getSchema(extensions: Extensions): Schema {
}) })
} }
return [extension.name, schema] return [extension.config.name, schema]
})) }))
const marks = Object.fromEntries(markExtensions.map(extension => { const marks = Object.fromEntries(markExtensions.map(extension => {
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name) const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.config.name)
const context = { options: extension.options } const context = { options: extension.options }
const schema: MarkSpec = cleanUpSchemaItem({ const schema: MarkSpec = cleanUpSchemaItem({
inclusive: callOrReturn(extension.inclusive, context), inclusive: callOrReturn(extension.config.inclusive, context),
excludes: callOrReturn(extension.excludes, context), excludes: callOrReturn(extension.config.excludes, context),
group: callOrReturn(extension.group, context), group: callOrReturn(extension.config.group, context),
spanning: callOrReturn(extension.spanning, context), spanning: callOrReturn(extension.config.spanning, context),
attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => { attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => {
return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }] return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }]
})), })),
}) })
if (extension.parseHTML) { if (extension.config.parseHTML) {
schema.parseDOM = extension.parseHTML schema.parseDOM = extension.config.parseHTML
.bind(context)() .bind(context)()
?.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes)) ?.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes))
} }
if (extension.renderHTML) { if (extension.config.renderHTML) {
schema.toDOM = mark => (extension.renderHTML as Function)?.bind(context)({ schema.toDOM = mark => (extension.config.renderHTML as Function)?.bind(context)({
mark, mark,
HTMLAttributes: mergeAttributes( HTMLAttributes: mergeAttributes(
extension.options.HTMLAttributes, extension.options.HTMLAttributes,
@ -90,7 +90,7 @@ export default function getSchema(extensions: Extensions): Schema {
}) })
} }
return [extension.name, schema] return [extension.config.name, schema]
})) }))
return new Schema({ return new Schema({

View File

@ -4,13 +4,13 @@ import callOrReturn from './callOrReturn'
export default function isList(name: string, extensions: Extensions) { export default function isList(name: string, extensions: Extensions) {
const { nodeExtensions } = splitExtensions(extensions) const { nodeExtensions } = splitExtensions(extensions)
const extension = nodeExtensions.find(item => item.name === name) const extension = nodeExtensions.find(item => item.config.name === name)
if (!extension) { if (!extension) {
return false return false
} }
const groups = callOrReturn(extension.group, { options: extension.options }) const groups = callOrReturn(extension.config.group, { options: extension.options })
if (typeof groups !== 'string') { if (typeof groups !== 'string') {
return false return false

View File

@ -4,9 +4,9 @@ import { NodeExtension } from '../NodeExtension'
import { MarkExtension } from '../MarkExtension' import { MarkExtension } from '../MarkExtension'
export default function splitExtensions(extensions: Extensions) { export default function splitExtensions(extensions: Extensions) {
const baseExtensions = extensions.filter(extension => extension.type === 'extension') as Extension[] const baseExtensions = extensions.filter(extension => extension instanceof Extension) as Extension[]
const nodeExtensions = extensions.filter(extension => extension.type === 'node') as NodeExtension[] const nodeExtensions = extensions.filter(extension => extension instanceof NodeExtension) as NodeExtension[]
const markExtensions = extensions.filter(extension => extension.type === 'mark') as MarkExtension[] const markExtensions = extensions.filter(extension => extension instanceof MarkExtension) as MarkExtension[]
return { return {
baseExtensions, baseExtensions,

View File

@ -19,9 +19,9 @@ describe('generateHTML', () => {
} }
const html = generateHTML(json, [ const html = generateHTML(json, [
Document(), Document,
Paragraph(), Paragraph,
Text(), Text,
]) ])
expect(html).to.eq('<p>Example Text</p>') expect(html).to.eq('<p>Example Text</p>')