Merge branch 'main' into feature/new-highlight-extension
# Conflicts: # packages/core/src/commands/toggleMark.ts
This commit is contained in:
@@ -8,17 +8,15 @@ import markIsActive from './utils/markIsActive'
|
||||
import getNodeAttrs from './utils/getNodeAttrs'
|
||||
import getMarkAttrs from './utils/getMarkAttrs'
|
||||
import removeElement from './utils/removeElement'
|
||||
import getSchemaTypeByName from './utils/getSchemaTypeByName'
|
||||
import getSchemaTypeNameByName from './utils/getSchemaTypeNameByName'
|
||||
import getHtmlFromFragment from './utils/getHtmlFromFragment'
|
||||
import createStyleTag from './utils/createStyleTag'
|
||||
import CommandManager from './CommandManager'
|
||||
import ExtensionManager from './ExtensionManager'
|
||||
import EventEmitter from './EventEmitter'
|
||||
import Extension from './Extension'
|
||||
import Node from './Node'
|
||||
import Mark from './Mark'
|
||||
import { Extensions, UnionToIntersection, PickValue } from './types'
|
||||
import defaultPlugins from './plugins'
|
||||
import * as coreCommands from './commands'
|
||||
import * as extensions from './extensions'
|
||||
import style from './style'
|
||||
|
||||
export type Command = (props: {
|
||||
@@ -37,19 +35,19 @@ export interface CommandsSpec {
|
||||
[key: string]: CommandSpec
|
||||
}
|
||||
|
||||
export interface Commands {}
|
||||
export interface AllExtensions {}
|
||||
|
||||
export type CommandNames = Extract<keyof Commands, string>
|
||||
export type AllCommands = UnionToIntersection<ReturnType<PickValue<ReturnType<AllExtensions[keyof AllExtensions]>, 'addCommands'>>>
|
||||
|
||||
export type SingleCommands = {
|
||||
[Item in keyof Commands]: Commands[Item] extends (...args: any[]) => any
|
||||
? (...args: Parameters<Commands[Item]>) => boolean
|
||||
[Item in keyof AllCommands]: AllCommands[Item] extends (...args: any[]) => any
|
||||
? (...args: Parameters<AllCommands[Item]>) => boolean
|
||||
: never
|
||||
}
|
||||
|
||||
export type ChainedCommands = {
|
||||
[Item in keyof Commands]: Commands[Item] extends (...args: any[]) => any
|
||||
? (...args: Parameters<Commands[Item]>) => ChainedCommands
|
||||
[Item in keyof AllCommands]: AllCommands[Item] extends (...args: any[]) => any
|
||||
? (...args: Parameters<AllCommands[Item]>) => ChainedCommands
|
||||
: never
|
||||
} & {
|
||||
run: () => boolean
|
||||
@@ -64,7 +62,7 @@ interface HTMLElement {
|
||||
interface EditorOptions {
|
||||
element: Element,
|
||||
content: EditorContent,
|
||||
extensions: (Extension | Node | Mark)[],
|
||||
extensions: Extensions,
|
||||
injectCSS: boolean,
|
||||
autoFocus: 'start' | 'end' | number | boolean | null,
|
||||
editable: boolean,
|
||||
@@ -117,12 +115,11 @@ export class Editor extends EventEmitter {
|
||||
this.createCommandManager()
|
||||
this.createExtensionManager()
|
||||
this.createSchema()
|
||||
this.extensionManager.resolveConfigs()
|
||||
this.createView()
|
||||
this.registerCommands(coreCommands)
|
||||
// this.registerCommands(coreCommands)
|
||||
this.injectCSS()
|
||||
|
||||
this.proxy.focus(this.options.autoFocus)
|
||||
window.setTimeout(() => this.proxy.focus(this.options.autoFocus), 0)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,7 +232,10 @@ export class Editor extends EventEmitter {
|
||||
* Creates an extension manager.
|
||||
*/
|
||||
private createExtensionManager() {
|
||||
this.extensionManager = new ExtensionManager(this.options.extensions, this.proxy)
|
||||
const coreExtensions = Object.entries(extensions).map(([, extension]) => extension())
|
||||
const allExtensions = [...coreExtensions, ...this.options.extensions]
|
||||
|
||||
this.extensionManager = new ExtensionManager(allExtensions, this.proxy)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,7 +346,7 @@ export class Editor extends EventEmitter {
|
||||
* @param attrs Attributes of the node or mark
|
||||
*/
|
||||
public isActive(name: string, attrs = {}) {
|
||||
const schemaType = getSchemaTypeByName(name, this.schema)
|
||||
const schemaType = getSchemaTypeNameByName(name, this.schema)
|
||||
|
||||
if (schemaType === 'node') {
|
||||
return nodeIsActive(this.state, this.schema.nodes[name], attrs)
|
||||
@@ -376,17 +376,24 @@ export class Editor extends EventEmitter {
|
||||
/**
|
||||
* Get the document as JSON.
|
||||
*/
|
||||
public json() {
|
||||
public getJSON() {
|
||||
return this.state.doc.toJSON()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document as HTML.
|
||||
*/
|
||||
public html() {
|
||||
public getHTML() {
|
||||
return getHtmlFromFragment(this.state.doc, this.schema)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is no content.
|
||||
*/
|
||||
public isEmpty() {
|
||||
return !this.state.doc.textContent.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the editor.
|
||||
*/
|
||||
|
||||
@@ -1,114 +1,113 @@
|
||||
import cloneDeep from 'clone-deep'
|
||||
import { Plugin } from 'prosemirror-state'
|
||||
import { Editor, CommandsSpec } from './Editor'
|
||||
import { Editor } from './Editor'
|
||||
import { GlobalAttributes } from './types'
|
||||
|
||||
type AnyObject = {
|
||||
[key: string]: any
|
||||
export interface ExtensionSpec<Options = {}, Commands = {}> {
|
||||
/**
|
||||
* Name
|
||||
*/
|
||||
name?: string,
|
||||
|
||||
/**
|
||||
* Default options
|
||||
*/
|
||||
defaultOptions?: Options,
|
||||
|
||||
/**
|
||||
* Global attributes
|
||||
*/
|
||||
addGlobalAttributes?: (this: {
|
||||
options: Options,
|
||||
}) => GlobalAttributes,
|
||||
|
||||
/**
|
||||
* Commands
|
||||
*/
|
||||
addCommands?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
}) => Commands,
|
||||
|
||||
/**
|
||||
* Keyboard shortcuts
|
||||
*/
|
||||
addKeyboardShortcuts?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
}) => {
|
||||
[key: string]: any
|
||||
},
|
||||
|
||||
/**
|
||||
* Input rules
|
||||
*/
|
||||
addInputRules?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
}) => any[],
|
||||
|
||||
/**
|
||||
* Paste rules
|
||||
*/
|
||||
addPasteRules?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
}) => any[],
|
||||
|
||||
/**
|
||||
* ProseMirror plugins
|
||||
*/
|
||||
addProseMirrorPlugins?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
}) => Plugin[],
|
||||
}
|
||||
|
||||
type NoInfer<T> = [T][T extends any ? 0 : never]
|
||||
/**
|
||||
* Extension interface for internal usage
|
||||
*/
|
||||
export type Extension = Required<Omit<ExtensionSpec, 'defaultOptions'> & {
|
||||
type: string,
|
||||
options: {
|
||||
[key: string]: any
|
||||
},
|
||||
}>
|
||||
|
||||
type MergeStrategy = 'extend' | 'overwrite'
|
||||
|
||||
type Configs = {
|
||||
[key: string]: {
|
||||
stategy: MergeStrategy
|
||||
value: any
|
||||
}[]
|
||||
/**
|
||||
* Default extension
|
||||
*/
|
||||
export const defaultExtension: Extension = {
|
||||
name: 'extension',
|
||||
type: 'extension',
|
||||
options: {},
|
||||
addGlobalAttributes: () => [],
|
||||
addCommands: () => ({}),
|
||||
addKeyboardShortcuts: () => ({}),
|
||||
addInputRules: () => [],
|
||||
addPasteRules: () => [],
|
||||
addProseMirrorPlugins: () => [],
|
||||
}
|
||||
|
||||
export interface ExtensionProps<Options> {
|
||||
name: string
|
||||
editor: Editor
|
||||
options: Options
|
||||
}
|
||||
|
||||
export interface ExtensionMethods<Props, Options> {
|
||||
name: string
|
||||
options: Options
|
||||
commands: (params: Props) => CommandsSpec
|
||||
inputRules: (params: Props) => any[]
|
||||
pasteRules: (params: Props) => any[]
|
||||
keys: (params: Props) => {
|
||||
[key: string]: Function
|
||||
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>)
|
||||
}
|
||||
plugins: (params: Props) => Plugin[]
|
||||
}
|
||||
|
||||
export default class Extension<
|
||||
Options = {},
|
||||
Props = ExtensionProps<Options>,
|
||||
Methods extends ExtensionMethods<Props, Options> = ExtensionMethods<Props, Options>,
|
||||
> {
|
||||
type = 'extension'
|
||||
const setOptions = (options?: Partial<Options>) => {
|
||||
const { defaultOptions, ...rest } = config
|
||||
|
||||
config: AnyObject = {}
|
||||
|
||||
configs: Configs = {}
|
||||
|
||||
options: Partial<Options> = {}
|
||||
|
||||
protected storeConfig(key: string, value: any, stategy: MergeStrategy) {
|
||||
const item = {
|
||||
stategy,
|
||||
value,
|
||||
}
|
||||
|
||||
if (this.configs[key]) {
|
||||
this.configs[key].push(item)
|
||||
} else {
|
||||
this.configs[key] = [item]
|
||||
return {
|
||||
...defaultExtension,
|
||||
...rest,
|
||||
options: {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
} as Options,
|
||||
}
|
||||
}
|
||||
|
||||
public configure(options: Partial<Options>) {
|
||||
this.options = { ...this.options, ...options }
|
||||
return this
|
||||
}
|
||||
|
||||
public name(value: Methods['name']) {
|
||||
this.storeConfig('name', value, 'overwrite')
|
||||
return this
|
||||
}
|
||||
|
||||
public defaults(value: Options) {
|
||||
this.storeConfig('defaults', value, 'overwrite')
|
||||
return this
|
||||
}
|
||||
|
||||
public commands(value: Methods['commands']) {
|
||||
this.storeConfig('commands', value, 'overwrite')
|
||||
return this
|
||||
}
|
||||
|
||||
public keys(value: Methods['keys']) {
|
||||
this.storeConfig('keys', value, 'overwrite')
|
||||
return this
|
||||
}
|
||||
|
||||
public inputRules(value: Methods['inputRules']) {
|
||||
this.storeConfig('inputRules', value, 'overwrite')
|
||||
return this
|
||||
}
|
||||
|
||||
public pasteRules(value: Methods['pasteRules']) {
|
||||
this.storeConfig('pasteRules', value, 'overwrite')
|
||||
return this
|
||||
}
|
||||
|
||||
public plugins(value: Methods['plugins']) {
|
||||
this.storeConfig('plugins', value, 'overwrite')
|
||||
return this
|
||||
}
|
||||
|
||||
public extend<T extends Extract<keyof Methods, string>>(key: T, value: Methods[T]) {
|
||||
this.storeConfig(key, value, 'extend')
|
||||
return this
|
||||
}
|
||||
|
||||
public create() {
|
||||
return <NewOptions = Options>(options?: Partial<NoInfer<NewOptions>>) => {
|
||||
return cloneDeep(this, true).configure(options as NewOptions)
|
||||
}
|
||||
}
|
||||
return Object.assign(setOptions, { config, extend })
|
||||
}
|
||||
|
||||
@@ -1,86 +1,53 @@
|
||||
import collect from 'collect.js'
|
||||
import { Plugin } from 'prosemirror-state'
|
||||
import { keymap } from 'prosemirror-keymap'
|
||||
import { Schema } from 'prosemirror-model'
|
||||
// import { Schema, Node as ProsemirrorNode } from 'prosemirror-model'
|
||||
import { inputRules } from 'prosemirror-inputrules'
|
||||
// import { EditorView, Decoration } from 'prosemirror-view'
|
||||
|
||||
import { Schema } from 'prosemirror-model'
|
||||
import { Editor } from './Editor'
|
||||
// import capitalize from './utils/capitalize'
|
||||
import { Extensions } from './types'
|
||||
import getTopNodeFromExtensions from './utils/getTopNodeFromExtensions'
|
||||
import getNodesFromExtensions from './utils/getNodesFromExtensions'
|
||||
import getMarksFromExtensions from './utils/getMarksFromExtensions'
|
||||
import resolveExtensionConfig from './utils/resolveExtensionConfig'
|
||||
import getSchema from './utils/getSchema'
|
||||
import getSchemaTypeByName from './utils/getSchemaTypeByName'
|
||||
|
||||
export default class ExtensionManager {
|
||||
|
||||
editor: Editor
|
||||
|
||||
schema: Schema
|
||||
|
||||
extensions: Extensions
|
||||
|
||||
constructor(extensions: Extensions, editor: Editor) {
|
||||
this.editor = editor
|
||||
this.extensions = extensions
|
||||
}
|
||||
this.schema = getSchema(this.extensions)
|
||||
|
||||
resolveConfigs() {
|
||||
this.extensions.forEach(extension => {
|
||||
const { editor } = this
|
||||
const { name } = extension.config
|
||||
const options = {
|
||||
...extension.config.defaults,
|
||||
...extension.options,
|
||||
const context = {
|
||||
options: extension.options,
|
||||
editor: this.editor,
|
||||
type: getSchemaTypeByName(extension.name, this.schema),
|
||||
}
|
||||
const type = extension.type === 'node'
|
||||
? editor.schema.nodes[name]
|
||||
: editor.schema.marks[name]
|
||||
|
||||
resolveExtensionConfig(extension, 'commands', {
|
||||
name, options, editor, type,
|
||||
})
|
||||
resolveExtensionConfig(extension, 'inputRules', {
|
||||
name, options, editor, type,
|
||||
})
|
||||
resolveExtensionConfig(extension, 'pasteRules', {
|
||||
name, options, editor, type,
|
||||
})
|
||||
resolveExtensionConfig(extension, 'keys', {
|
||||
name, options, editor, type,
|
||||
})
|
||||
resolveExtensionConfig(extension, 'plugins', {
|
||||
name, options, editor, type,
|
||||
})
|
||||
const commands = extension.addCommands.bind(context)()
|
||||
|
||||
if (extension.config.commands) {
|
||||
editor.registerCommands(extension.config.commands)
|
||||
}
|
||||
editor.registerCommands(commands)
|
||||
})
|
||||
}
|
||||
|
||||
get schema(): Schema {
|
||||
return getSchema(this.extensions)
|
||||
}
|
||||
|
||||
get topNode(): any {
|
||||
return getTopNodeFromExtensions(this.extensions)
|
||||
}
|
||||
|
||||
get nodes(): any {
|
||||
return getNodesFromExtensions(this.extensions)
|
||||
}
|
||||
|
||||
get marks(): any {
|
||||
return getMarksFromExtensions(this.extensions)
|
||||
}
|
||||
|
||||
get plugins(): Plugin[] {
|
||||
const plugins = collect(this.extensions)
|
||||
.flatMap(extension => extension.config.plugins)
|
||||
.filter(plugin => plugin)
|
||||
.toArray()
|
||||
const plugins = this.extensions
|
||||
.map(extension => {
|
||||
const context = {
|
||||
options: extension.options,
|
||||
editor: this.editor,
|
||||
type: getSchemaTypeByName(extension.name, this.schema),
|
||||
}
|
||||
|
||||
return extension.addProseMirrorPlugins.bind(context)()
|
||||
})
|
||||
.flat()
|
||||
|
||||
return [
|
||||
...plugins,
|
||||
@@ -91,25 +58,43 @@ export default class ExtensionManager {
|
||||
}
|
||||
|
||||
get inputRules(): any {
|
||||
return collect(this.extensions)
|
||||
.flatMap(extension => extension.config.inputRules)
|
||||
.filter(plugin => plugin)
|
||||
.toArray()
|
||||
return this.extensions
|
||||
.map(extension => {
|
||||
const context = {
|
||||
options: extension.options,
|
||||
editor: this.editor,
|
||||
type: getSchemaTypeByName(extension.name, this.schema),
|
||||
}
|
||||
|
||||
return extension.addInputRules.bind(context)()
|
||||
})
|
||||
.flat()
|
||||
}
|
||||
|
||||
get pasteRules(): any {
|
||||
return collect(this.extensions)
|
||||
.flatMap(extension => extension.config.pasteRules)
|
||||
.filter(plugin => plugin)
|
||||
.toArray()
|
||||
return this.extensions
|
||||
.map(extension => {
|
||||
const context = {
|
||||
options: extension.options,
|
||||
editor: this.editor,
|
||||
type: getSchemaTypeByName(extension.name, this.schema),
|
||||
}
|
||||
|
||||
return extension.addPasteRules.bind(context)()
|
||||
})
|
||||
.flat()
|
||||
}
|
||||
|
||||
get keymaps() {
|
||||
return collect(this.extensions)
|
||||
.map(extension => extension.config.keys)
|
||||
.filter(keys => keys)
|
||||
.map(keys => keymap(keys))
|
||||
.toArray()
|
||||
return this.extensions.map(extension => {
|
||||
const context = {
|
||||
options: extension.options,
|
||||
editor: this.editor,
|
||||
type: getSchemaTypeByName(extension.name, this.schema),
|
||||
}
|
||||
|
||||
return keymap(extension.addKeyboardShortcuts.bind(context)())
|
||||
})
|
||||
}
|
||||
|
||||
get nodeViews() {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { MarkSpec, MarkType } from 'prosemirror-model'
|
||||
import Extension, { ExtensionMethods } from './Extension'
|
||||
import { Editor } from './Editor'
|
||||
|
||||
export interface MarkProps<Options> {
|
||||
name: string
|
||||
editor: Editor
|
||||
options: Options
|
||||
type: MarkType
|
||||
}
|
||||
|
||||
export interface MarkMethods<Props, Options> extends ExtensionMethods<Props, Options> {
|
||||
topMark: boolean
|
||||
schema: (params: Omit<Props, 'type' | 'editor'>) => MarkSpec
|
||||
}
|
||||
|
||||
export default class Mark<
|
||||
Options = {},
|
||||
Props = MarkProps<Options>,
|
||||
Methods extends MarkMethods<Props, Options> = MarkMethods<Props, Options>,
|
||||
> extends Extension<Options, Props, Methods> {
|
||||
type = 'mark'
|
||||
|
||||
public schema(value: Methods['schema']) {
|
||||
this.storeConfig('schema', value, 'overwrite')
|
||||
return this
|
||||
}
|
||||
}
|
||||
151
packages/core/src/MarkExtension.ts
Normal file
151
packages/core/src/MarkExtension.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import {
|
||||
DOMOutputSpec, MarkSpec, Mark, MarkType,
|
||||
} from 'prosemirror-model'
|
||||
import { Plugin } from 'prosemirror-state'
|
||||
import { ExtensionSpec, defaultExtension } from './Extension'
|
||||
import { Attributes, Overwrite } from './types'
|
||||
import { Editor } from './Editor'
|
||||
|
||||
export interface MarkExtensionSpec<Options = {}, Commands = {}> extends Overwrite<ExtensionSpec<Options, Commands>, {
|
||||
/**
|
||||
* Inclusive
|
||||
*/
|
||||
inclusive?: MarkSpec['inclusive'],
|
||||
|
||||
/**
|
||||
* Excludes
|
||||
*/
|
||||
excludes?: MarkSpec['excludes'],
|
||||
|
||||
/**
|
||||
* Group
|
||||
*/
|
||||
group?: MarkSpec['group'],
|
||||
|
||||
/**
|
||||
* Spanning
|
||||
*/
|
||||
spanning?: MarkSpec['spanning'],
|
||||
|
||||
/**
|
||||
* Parse HTML
|
||||
*/
|
||||
parseHTML?: (
|
||||
this: {
|
||||
options: Options,
|
||||
},
|
||||
) => MarkSpec['parseDOM'],
|
||||
|
||||
/**
|
||||
* Render HTML
|
||||
*/
|
||||
renderHTML?: ((
|
||||
this: {
|
||||
options: Options,
|
||||
},
|
||||
props: {
|
||||
mark: Mark,
|
||||
attributes: { [key: string]: any },
|
||||
}
|
||||
) => DOMOutputSpec) | null,
|
||||
|
||||
/**
|
||||
* Attributes
|
||||
*/
|
||||
addAttributes?: (
|
||||
this: {
|
||||
options: Options,
|
||||
},
|
||||
) => Attributes,
|
||||
|
||||
/**
|
||||
* Commands
|
||||
*/
|
||||
addCommands?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
type: MarkType,
|
||||
}) => Commands,
|
||||
|
||||
/**
|
||||
* Keyboard shortcuts
|
||||
*/
|
||||
addKeyboardShortcuts?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
type: MarkType,
|
||||
}) => {
|
||||
[key: string]: any
|
||||
},
|
||||
|
||||
/**
|
||||
* Input rules
|
||||
*/
|
||||
addInputRules?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
type: MarkType,
|
||||
}) => any[],
|
||||
|
||||
/**
|
||||
* Paste rules
|
||||
*/
|
||||
addPasteRules?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
type: MarkType,
|
||||
}) => any[],
|
||||
|
||||
/**
|
||||
* ProseMirror plugins
|
||||
*/
|
||||
addProseMirrorPlugins?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
type: MarkType,
|
||||
}) => Plugin[],
|
||||
}> {}
|
||||
|
||||
export type MarkExtension = Required<Omit<MarkExtensionSpec, 'defaultOptions'> & {
|
||||
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 })
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { NodeSpec, NodeType } from 'prosemirror-model'
|
||||
import Extension, { ExtensionMethods } from './Extension'
|
||||
import { Editor } from './Editor'
|
||||
|
||||
export interface NodeProps<Options> {
|
||||
name: string
|
||||
editor: Editor
|
||||
options: Options
|
||||
type: NodeType
|
||||
}
|
||||
|
||||
export interface NodeMethods<Props, Options> extends ExtensionMethods<Props, Options> {
|
||||
topNode: boolean
|
||||
schema: (params: Omit<Props, 'type' | 'editor'>) => NodeSpec
|
||||
}
|
||||
|
||||
export default class Node<
|
||||
Options = {},
|
||||
Props = NodeProps<Options>,
|
||||
Methods extends NodeMethods<Props, Options> = NodeMethods<Props, Options>,
|
||||
> extends Extension<Options, Props, Methods> {
|
||||
type = 'node'
|
||||
|
||||
public topNode(value: Methods['topNode'] = true) {
|
||||
this.storeConfig('topNode', value, 'overwrite')
|
||||
return this
|
||||
}
|
||||
|
||||
public schema(value: Methods['schema']) {
|
||||
this.storeConfig('schema', value, 'overwrite')
|
||||
return this
|
||||
}
|
||||
}
|
||||
193
packages/core/src/NodeExtension.ts
Normal file
193
packages/core/src/NodeExtension.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import {
|
||||
DOMOutputSpec, NodeSpec, Node, NodeType,
|
||||
} from 'prosemirror-model'
|
||||
import { Plugin } from 'prosemirror-state'
|
||||
import { ExtensionSpec, defaultExtension } from './Extension'
|
||||
import { Attributes, Overwrite } from './types'
|
||||
import { Editor } from './Editor'
|
||||
|
||||
export interface NodeExtensionSpec<Options = {}, Commands = {}> extends Overwrite<ExtensionSpec<Options, Commands>, {
|
||||
/**
|
||||
* TopNode
|
||||
*/
|
||||
topNode?: boolean,
|
||||
|
||||
/**
|
||||
* Content
|
||||
*/
|
||||
content?: NodeSpec['content'],
|
||||
|
||||
/**
|
||||
* Marks
|
||||
*/
|
||||
marks?: NodeSpec['marks'],
|
||||
|
||||
/**
|
||||
* Group
|
||||
*/
|
||||
group?: NodeSpec['group'],
|
||||
|
||||
/**
|
||||
* Inline
|
||||
*/
|
||||
inline?: NodeSpec['inline'],
|
||||
|
||||
/**
|
||||
* Atom
|
||||
*/
|
||||
atom?: NodeSpec['atom'],
|
||||
|
||||
/**
|
||||
* Selectable
|
||||
*/
|
||||
selectable?: NodeSpec['selectable'],
|
||||
|
||||
/**
|
||||
* Draggable
|
||||
*/
|
||||
draggable?: NodeSpec['draggable'],
|
||||
|
||||
/**
|
||||
* Code
|
||||
*/
|
||||
code?: NodeSpec['code'],
|
||||
|
||||
/**
|
||||
* Defining
|
||||
*/
|
||||
defining?: NodeSpec['defining'],
|
||||
|
||||
/**
|
||||
* Isolating
|
||||
*/
|
||||
isolating?: NodeSpec['isolating'],
|
||||
|
||||
/**
|
||||
* Parse HTML
|
||||
*/
|
||||
parseHTML?: (
|
||||
this: {
|
||||
options: Options,
|
||||
},
|
||||
) => NodeSpec['parseDOM'],
|
||||
|
||||
/**
|
||||
* Render HTML
|
||||
*/
|
||||
renderHTML?: ((
|
||||
this: {
|
||||
options: Options,
|
||||
},
|
||||
props: {
|
||||
node: Node,
|
||||
attributes: { [key: string]: any },
|
||||
}
|
||||
) => DOMOutputSpec) | null,
|
||||
|
||||
/**
|
||||
* Add Attributes
|
||||
*/
|
||||
addAttributes?: (
|
||||
this: {
|
||||
options: Options,
|
||||
},
|
||||
) => Attributes,
|
||||
|
||||
/**
|
||||
* Commands
|
||||
*/
|
||||
addCommands?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
type: NodeType,
|
||||
}) => Commands,
|
||||
|
||||
/**
|
||||
* Keyboard shortcuts
|
||||
*/
|
||||
addKeyboardShortcuts?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
type: NodeType,
|
||||
}) => {
|
||||
[key: string]: any
|
||||
},
|
||||
|
||||
/**
|
||||
* Input rules
|
||||
*/
|
||||
addInputRules?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
type: NodeType,
|
||||
}) => any[],
|
||||
|
||||
/**
|
||||
* Paste rules
|
||||
*/
|
||||
addPasteRules?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
type: NodeType,
|
||||
}) => any[],
|
||||
|
||||
/**
|
||||
* ProseMirror plugins
|
||||
*/
|
||||
addProseMirrorPlugins?: (this: {
|
||||
options: Options,
|
||||
editor: Editor,
|
||||
type: NodeType,
|
||||
}) => Plugin[],
|
||||
}> {}
|
||||
|
||||
export type NodeExtension = Required<Omit<NodeExtensionSpec, 'defaultOptions'> & {
|
||||
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: () => ({}),
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
type BlurCommand = () => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
blur: BlurCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const blur: BlurCommand = () => ({ view }) => {
|
||||
const element = view.dom as HTMLElement
|
||||
|
||||
element.blur()
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
type ClearContentCommand = (emitUpdate?: Boolean) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
clearContent: ClearContentCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const clearContent: ClearContentCommand = (emitUpdate = false) => ({ commands }) => {
|
||||
return commands.setContent('', emitUpdate)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { deleteSelection as originalDeleteSelection } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
|
||||
type DeleteSelectionCommand = () => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
deleteSelection: DeleteSelectionCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteSelection: DeleteSelectionCommand = () => ({ state, dispatch }) => {
|
||||
return originalDeleteSelection(state, dispatch)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
export { blur } from './blur'
|
||||
export { clearContent } from './clearContent'
|
||||
export { deleteSelection } from './deleteSelection'
|
||||
export { focus } from './focus'
|
||||
export { insertHTML } from './insertHTML'
|
||||
export { insertText } from './insertText'
|
||||
export { liftListItem } from './liftListItem'
|
||||
export { removeMark } from './removeMark'
|
||||
export { removeMarks } from './removeMarks'
|
||||
export { scrollIntoView } from './scrollIntoView'
|
||||
export { selectAll } from './selectAll'
|
||||
export { selectParentNode } from './selectParentNode'
|
||||
export { setBlockType } from './setBlockType'
|
||||
export { setContent } from './setContent'
|
||||
export { sinkListItem } from './sinkListItem'
|
||||
export { splitListItem } from './splitListItem'
|
||||
export { toggleBlockType } from './toggleBlockType'
|
||||
export { toggleList } from './toggleList'
|
||||
export { toggleMark } from './toggleMark'
|
||||
export { updateMark } from './updateMark'
|
||||
export { toggleWrap } from './toggleWrap'
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
type InsertTextCommand = (value: string) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
insertText: InsertTextCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const insertText: InsertTextCommand = value => ({ tr }) => {
|
||||
tr.insertText(value)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { liftListItem as originalLiftListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
type LiftListItem = (typeOrName: string | NodeType) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
liftListItem: LiftListItem,
|
||||
}
|
||||
}
|
||||
|
||||
export const liftListItem: LiftListItem = typeOrName => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return originalLiftListItem(type)(state, dispatch)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { MarkType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getMarkType from '../utils/getMarkType'
|
||||
import getMarkRange from '../utils/getMarkRange'
|
||||
|
||||
type RemoveMarkCommand = (typeOrName: string | MarkType) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
removeMark: RemoveMarkCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const removeMark: RemoveMarkCommand = typeOrName => ({ tr, state }) => {
|
||||
const { selection } = tr
|
||||
const type = getMarkType(typeOrName, state.schema)
|
||||
let { from, to } = selection
|
||||
const { $from, empty } = selection
|
||||
|
||||
if (empty) {
|
||||
const range = getMarkRange($from, type)
|
||||
|
||||
if (range) {
|
||||
from = range.from
|
||||
to = range.to
|
||||
}
|
||||
}
|
||||
|
||||
tr.removeMark(from, to, type)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
type RemoveMarksCommand = () => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
removeMarks: RemoveMarksCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const removeMarks: RemoveMarksCommand = () => ({ tr, state }) => {
|
||||
const { selection } = tr
|
||||
const { from, to, empty } = selection
|
||||
|
||||
if (empty) {
|
||||
return true
|
||||
}
|
||||
|
||||
Object
|
||||
.entries(state.schema.marks)
|
||||
.forEach(([, mark]) => {
|
||||
tr.removeMark(from, to, mark as any)
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
type ScrollIntoViewCommand = () => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
scrollIntoView: ScrollIntoViewCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const scrollIntoView: ScrollIntoViewCommand = () => ({ tr }) => {
|
||||
tr.scrollIntoView()
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { selectAll as originalSelectAll } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
|
||||
type SelectAllCommand = () => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
selectAll: SelectAllCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const selectAll: SelectAllCommand = () => ({ state, dispatch }) => {
|
||||
return originalSelectAll(state, dispatch)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { selectParentNode as originalSelectParentNode } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
|
||||
type SelectParentNodeCommand = () => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
selectParentNode: SelectParentNodeCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const selectParentNode: SelectParentNodeCommand = () => ({ state, dispatch }) => {
|
||||
return originalSelectParentNode(state, dispatch)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { setBlockType as originalSetBlockType } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
type SetBlockTypeCommand = (
|
||||
typeOrName: string | NodeType,
|
||||
attrs?: {},
|
||||
) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
setBlockType: SetBlockTypeCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const setBlockType: SetBlockTypeCommand = (typeOrName, attrs = {}) => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return originalSetBlockType(type, attrs)(state, dispatch)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { TextSelection } from 'prosemirror-state'
|
||||
import { Command } from '../Editor'
|
||||
|
||||
type SetContentCommand = (
|
||||
content: string,
|
||||
emitUpdate?: Boolean,
|
||||
parseOptions?: any,
|
||||
) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
setContent: SetContentCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const setContent: SetContentCommand = (content = '', emitUpdate = false, parseOptions = {}) => ({ tr, editor }) => {
|
||||
const { createDocument } = editor
|
||||
const { doc } = tr
|
||||
const document = createDocument(content, parseOptions)
|
||||
const selection = TextSelection.create(doc, 0, doc.content.size)
|
||||
|
||||
tr.setSelection(selection)
|
||||
.replaceSelectionWith(document, false)
|
||||
.setMeta('preventUpdate', !emitUpdate)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { sinkListItem as originalSinkListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
type SinkListItem = (typeOrName: string | NodeType) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
sinkListItem: SinkListItem,
|
||||
}
|
||||
}
|
||||
|
||||
export const sinkListItem: SinkListItem = typeOrName => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return originalSinkListItem(type)(state, dispatch)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { splitListItem as originalSplitListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
type SplitListItem = (typeOrName: string | NodeType) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
splitListItem: SplitListItem,
|
||||
}
|
||||
}
|
||||
|
||||
export const splitListItem: SplitListItem = typeOrName => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return originalSplitListItem(type)(state, dispatch)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import nodeIsActive from '../utils/nodeIsActive'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
type ToggleBlockTypeCommand = (
|
||||
typeOrName: string | NodeType,
|
||||
toggleType: string | NodeType,
|
||||
attrs?: {}
|
||||
) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
toggleBlockType: ToggleBlockTypeCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const toggleBlockType: ToggleBlockTypeCommand = (typeOrName, toggleTypeOrName, attrs = {}) => ({ state, commands }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
const toggleType = getNodeType(toggleTypeOrName, state.schema)
|
||||
const isActive = nodeIsActive(state, type, attrs)
|
||||
|
||||
if (isActive) {
|
||||
return commands.setBlockType(toggleType)
|
||||
}
|
||||
|
||||
return commands.setBlockType(type, attrs)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { wrapInList, liftListItem } from 'prosemirror-schema-list'
|
||||
import { findParentNode } from 'prosemirror-utils'
|
||||
import { Node, NodeType, Schema } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
type ToggleListCommand = (
|
||||
listType: string | NodeType,
|
||||
itemType: string | NodeType,
|
||||
) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
toggleList: ToggleListCommand,
|
||||
}
|
||||
}
|
||||
|
||||
function isList(node: Node, schema: Schema) {
|
||||
return (node.type === schema.nodes.bullet_list
|
||||
|| node.type === schema.nodes.ordered_list
|
||||
|| node.type === schema.nodes.todo_list)
|
||||
}
|
||||
|
||||
export const toggleList: ToggleListCommand = (listTypeOrName, itemTypeOrName) => ({ tr, state, dispatch }) => {
|
||||
const listType = getNodeType(listTypeOrName, state.schema)
|
||||
const itemType = getNodeType(itemTypeOrName, state.schema)
|
||||
const { schema, selection } = state
|
||||
const { $from, $to } = selection
|
||||
const range = $from.blockRange($to)
|
||||
|
||||
if (!range) {
|
||||
return false
|
||||
}
|
||||
|
||||
const parentList = findParentNode(node => isList(node, schema))(selection)
|
||||
|
||||
if (range.depth >= 1 && parentList && range.depth - parentList.depth <= 1) {
|
||||
if (parentList.node.type === listType) {
|
||||
return liftListItem(itemType)(state, dispatch)
|
||||
}
|
||||
|
||||
if (isList(parentList.node, schema) && listType.validContent(parentList.node.content)) {
|
||||
tr.setNodeMarkup(parentList.pos, listType)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return wrapInList(listType)(state, dispatch)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { wrapIn, lift } from 'prosemirror-commands'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import nodeIsActive from '../utils/nodeIsActive'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
type ToggleWrapCommand = (typeOrName: string | NodeType, attrs?: {}) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
toggleWrap: ToggleWrapCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const toggleWrap: ToggleWrapCommand = (typeOrName, attrs) => ({
|
||||
state, dispatch,
|
||||
}) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
const isActive = nodeIsActive(state, type, attrs)
|
||||
|
||||
if (isActive) {
|
||||
return lift(state, dispatch)
|
||||
}
|
||||
|
||||
return wrapIn(type, attrs)(state, dispatch)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { MarkType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getMarkType from '../utils/getMarkType'
|
||||
import getMarkRange from '../utils/getMarkRange'
|
||||
|
||||
type UpdateMarkCommand = (
|
||||
typeOrName: string | MarkType,
|
||||
attrs: {},
|
||||
) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
updateMark: UpdateMarkCommand,
|
||||
}
|
||||
}
|
||||
|
||||
export const updateMark: UpdateMarkCommand = (typeOrName, attrs = {}) => ({ tr, state }) => {
|
||||
const { selection, doc } = tr
|
||||
let { from, to } = selection
|
||||
const { $from, empty } = selection
|
||||
const type = getMarkType(typeOrName, state.schema)
|
||||
|
||||
if (empty) {
|
||||
const range = getMarkRange($from, type)
|
||||
|
||||
if (range) {
|
||||
from = range.from
|
||||
to = range.to
|
||||
}
|
||||
}
|
||||
|
||||
const hasMark = doc.rangeHasMark(from, to, type)
|
||||
|
||||
if (hasMark) {
|
||||
tr.removeMark(from, to, type)
|
||||
}
|
||||
|
||||
tr.addMark(from, to, type.create(attrs))
|
||||
|
||||
return true
|
||||
}
|
||||
22
packages/core/src/extensions/blur.ts
Normal file
22
packages/core/src/extensions/blur.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const Blur = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
blur: (): Command => ({ view }) => {
|
||||
const element = view.dom as HTMLElement
|
||||
|
||||
element.blur()
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
Blur: typeof Blur,
|
||||
}
|
||||
}
|
||||
18
packages/core/src/extensions/clearContent.ts
Normal file
18
packages/core/src/extensions/clearContent.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const ClearContent = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
clearContent: (emitUpdate: Boolean = false): Command => ({ commands }) => {
|
||||
return commands.setContent('', emitUpdate)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ClearContent: typeof ClearContent,
|
||||
}
|
||||
}
|
||||
19
packages/core/src/extensions/deleteSelection.ts
Normal file
19
packages/core/src/extensions/deleteSelection.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { deleteSelection } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const DeleteSelection = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
deleteSelection: (): Command => ({ state, dispatch }) => {
|
||||
return deleteSelection(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
DeleteSelection: typeof DeleteSelection,
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,9 @@
|
||||
import { TextSelection } from 'prosemirror-state'
|
||||
import { Editor, Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import minMax from '../utils/minMax'
|
||||
|
||||
type Position = 'start' | 'end' | number | boolean | null
|
||||
type FocusCommand = (position?: Position) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
focus: FocusCommand
|
||||
}
|
||||
}
|
||||
|
||||
interface ResolvedSelection {
|
||||
from: number,
|
||||
@@ -43,19 +37,31 @@ function resolveSelection(editor: Editor, position: Position = null): ResolvedSe
|
||||
}
|
||||
}
|
||||
|
||||
export const focus: FocusCommand = (position = null) => ({ editor, view, tr }) => {
|
||||
if ((view.hasFocus() && position === null) || position === false) {
|
||||
return true
|
||||
export const Focus = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
focus: (position: Position = null): Command => ({ editor, view, tr }) => {
|
||||
if ((view.hasFocus() && position === null) || position === false) {
|
||||
return true
|
||||
}
|
||||
|
||||
const { from, to } = resolveSelection(editor, position)
|
||||
const { doc } = tr
|
||||
const resolvedFrom = minMax(from, 0, doc.content.size)
|
||||
const resolvedEnd = minMax(to, 0, doc.content.size)
|
||||
const selection = TextSelection.create(doc, resolvedFrom, resolvedEnd)
|
||||
|
||||
tr.setSelection(selection)
|
||||
view.focus()
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
Focus: typeof Focus,
|
||||
}
|
||||
|
||||
const { from, to } = resolveSelection(editor, position)
|
||||
const { doc } = tr
|
||||
const resolvedFrom = minMax(from, 0, doc.content.size)
|
||||
const resolvedEnd = minMax(to, 0, doc.content.size)
|
||||
const selection = TextSelection.create(doc, resolvedFrom, resolvedEnd)
|
||||
|
||||
tr.setSelection(selection)
|
||||
view.focus()
|
||||
|
||||
return true
|
||||
}
|
||||
23
packages/core/src/extensions/index.ts
Normal file
23
packages/core/src/extensions/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export { Blur } from './blur'
|
||||
export { ClearContent } from './clearContent'
|
||||
export { DeleteSelection } from './deleteSelection'
|
||||
export { Focus } from './focus'
|
||||
export { InsertHTML } from './insertHTML'
|
||||
export { InsertText } from './insertText'
|
||||
export { LiftListItem } from './liftListItem'
|
||||
export { RemoveMark } from './removeMark'
|
||||
export { RemoveMarks } from './removeMarks'
|
||||
export { ScrollIntoView } from './scrollIntoView'
|
||||
export { SelectAll } from './selectAll'
|
||||
export { SelectParentNode } from './selectParentNode'
|
||||
export { SetNodeAttributes } from './setNodeAttributes'
|
||||
export { SetBlockType } from './setBlockType'
|
||||
export { SetContent } from './setContent'
|
||||
export { SinkListItem } from './sinkListItem'
|
||||
export { SplitBlock } from './splitBlock'
|
||||
export { SplitListItem } from './splitListItem'
|
||||
export { ToggleBlockType } from './toggleBlockType'
|
||||
export { ToggleList } from './toggleList'
|
||||
export { ToggleMark } from './toggleMark'
|
||||
export { UpdateMark } from './updateMark'
|
||||
export { ToggleWrap } from './toggleWrap'
|
||||
@@ -1,16 +1,9 @@
|
||||
import { DOMParser } from 'prosemirror-model'
|
||||
import { Selection, Transaction } from 'prosemirror-state'
|
||||
import { ReplaceStep, ReplaceAroundStep } from 'prosemirror-transform'
|
||||
import { Command } from '../Editor'
|
||||
import elementFromString from '../utils/elementFromString'
|
||||
|
||||
type InsertHTMLCommand = (value: string) => Command
|
||||
|
||||
declare module '../Editor' {
|
||||
interface Commands {
|
||||
insertHTML: InsertHTMLCommand,
|
||||
}
|
||||
}
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
// TODO: move to utils
|
||||
// https://github.com/ProseMirror/prosemirror-state/blob/master/src/selection.js#L466
|
||||
@@ -25,13 +18,25 @@ function selectionToInsertionEnd(tr: Transaction, startLen: number, bias: number
|
||||
tr.setSelection(Selection.near(tr.doc.resolve(end as unknown as number), bias))
|
||||
}
|
||||
|
||||
export const insertHTML: InsertHTMLCommand = value => ({ tr, state }) => {
|
||||
const { selection } = tr
|
||||
const element = elementFromString(value)
|
||||
const slice = DOMParser.fromSchema(state.schema).parseSlice(element)
|
||||
export const InsertHTML = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
insertHTML: (value: string): Command => ({ tr, state }) => {
|
||||
const { selection } = tr
|
||||
const element = elementFromString(value)
|
||||
const slice = DOMParser.fromSchema(state.schema).parseSlice(element)
|
||||
|
||||
tr.insert(selection.anchor, slice.content)
|
||||
selectionToInsertionEnd(tr, tr.steps.length - 1, -1)
|
||||
tr.insert(selection.anchor, slice.content)
|
||||
selectionToInsertionEnd(tr, tr.steps.length - 1, -1)
|
||||
|
||||
return true
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
InsertHTML: typeof InsertHTML,
|
||||
}
|
||||
}
|
||||
20
packages/core/src/extensions/insertText.ts
Normal file
20
packages/core/src/extensions/insertText.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const InsertText = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
insertText: (value: string): Command => ({ tr }) => {
|
||||
tr.insertText(value)
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
InsertText: typeof InsertText,
|
||||
}
|
||||
}
|
||||
23
packages/core/src/extensions/liftListItem.ts
Normal file
23
packages/core/src/extensions/liftListItem.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { liftListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const LiftListItem = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
liftListItem: (typeOrName: string | NodeType): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return liftListItem(type)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
LiftListItem: typeof LiftListItem,
|
||||
}
|
||||
}
|
||||
37
packages/core/src/extensions/removeMark.ts
Normal file
37
packages/core/src/extensions/removeMark.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { MarkType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getMarkType from '../utils/getMarkType'
|
||||
import getMarkRange from '../utils/getMarkRange'
|
||||
|
||||
export const RemoveMark = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
removeMark: (typeOrName: string | MarkType): Command => ({ tr, state }) => {
|
||||
const { selection } = tr
|
||||
const type = getMarkType(typeOrName, state.schema)
|
||||
let { from, to } = selection
|
||||
const { $from, empty } = selection
|
||||
|
||||
if (empty) {
|
||||
const range = getMarkRange($from, type)
|
||||
|
||||
if (range) {
|
||||
from = range.from
|
||||
to = range.to
|
||||
}
|
||||
}
|
||||
|
||||
tr.removeMark(from, to, type)
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
RemoveMark: typeof RemoveMark,
|
||||
}
|
||||
}
|
||||
31
packages/core/src/extensions/removeMarks.ts
Normal file
31
packages/core/src/extensions/removeMarks.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const RemoveMarks = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
removeMarks: (): Command => ({ tr, state }) => {
|
||||
const { selection } = tr
|
||||
const { from, to, empty } = selection
|
||||
|
||||
if (empty) {
|
||||
return true
|
||||
}
|
||||
|
||||
Object
|
||||
.entries(state.schema.marks)
|
||||
.forEach(([, mark]) => {
|
||||
tr.removeMark(from, to, mark as any)
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
RemoveMarks: typeof RemoveMarks,
|
||||
}
|
||||
}
|
||||
20
packages/core/src/extensions/scrollIntoView.ts
Normal file
20
packages/core/src/extensions/scrollIntoView.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const ScrollIntoView = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
scrollIntoView: (): Command => ({ tr }) => {
|
||||
tr.scrollIntoView()
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ScrollIntoView: typeof ScrollIntoView,
|
||||
}
|
||||
}
|
||||
19
packages/core/src/extensions/selectAll.ts
Normal file
19
packages/core/src/extensions/selectAll.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { selectAll } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const SelectAll = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
selectAll: (): Command => ({ state, dispatch }) => {
|
||||
return selectAll(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SelectAll: typeof SelectAll,
|
||||
}
|
||||
}
|
||||
19
packages/core/src/extensions/selectParentNode.ts
Normal file
19
packages/core/src/extensions/selectParentNode.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { selectParentNode } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const SelectParentNode = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
selectParentNode: (): Command => ({ state, dispatch }) => {
|
||||
return selectParentNode(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SelectParentNode: typeof SelectParentNode,
|
||||
}
|
||||
}
|
||||
23
packages/core/src/extensions/setBlockType.ts
Normal file
23
packages/core/src/extensions/setBlockType.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { setBlockType } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const SetBlockType = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
setBlockType: (typeOrName: string | NodeType, attrs = {}): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return setBlockType(type, attrs)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SetBlockType: typeof SetBlockType,
|
||||
}
|
||||
}
|
||||
28
packages/core/src/extensions/setContent.ts
Normal file
28
packages/core/src/extensions/setContent.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { TextSelection } from 'prosemirror-state'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const SetContent = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
setContent: (content: string, emitUpdate: Boolean = false, parseOptions = {}): Command => ({ tr, editor }) => {
|
||||
const { createDocument } = editor
|
||||
const { doc } = tr
|
||||
const document = createDocument(content, parseOptions)
|
||||
const selection = TextSelection.create(doc, 0, doc.content.size)
|
||||
|
||||
tr.setSelection(selection)
|
||||
.replaceSelectionWith(document, false)
|
||||
.setMeta('preventUpdate', !emitUpdate)
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SetContent: typeof SetContent,
|
||||
}
|
||||
}
|
||||
27
packages/core/src/extensions/setNodeAttributes.ts
Normal file
27
packages/core/src/extensions/setNodeAttributes.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const SetNodeAttributes = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
setNodeAttributes: (attributes: {}): Command => ({ tr, state }) => {
|
||||
const { selection } = tr
|
||||
const { from, to } = selection
|
||||
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (!node.type.isText) {
|
||||
tr.setNodeMarkup(pos, undefined, attributes)
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SetNodeAttributes: typeof SetNodeAttributes,
|
||||
}
|
||||
}
|
||||
23
packages/core/src/extensions/sinkListItem.ts
Normal file
23
packages/core/src/extensions/sinkListItem.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { sinkListItem as originalSinkListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const SinkListItem = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
sinkListItem: (typeOrName: string | NodeType): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return originalSinkListItem(type)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SinkListItem: typeof SinkListItem,
|
||||
}
|
||||
}
|
||||
71
packages/core/src/extensions/splitBlock.ts
Normal file
71
packages/core/src/extensions/splitBlock.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// import {
|
||||
// baseKeymap, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock,
|
||||
// } from 'prosemirror-commands'
|
||||
import { canSplit } from 'prosemirror-transform'
|
||||
import { ContentMatch, Fragment } from 'prosemirror-model'
|
||||
import { NodeSelection, TextSelection } from 'prosemirror-state'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
// import getNodeType from '../utils/getNodeType'
|
||||
|
||||
function defaultBlockAt(match: ContentMatch) {
|
||||
for (let i = 0; i < match.edgeCount; i + 1) {
|
||||
const { type } = match.edge(i)
|
||||
// @ts-ignore
|
||||
if (type.isTextblock && !type.hasRequiredAttrs()) return type
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export const SplitBlock = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
splitBlock: (copyAttributes = false): Command => ({ state, dispatch }) => {
|
||||
// const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
const { $from, $to } = state.selection
|
||||
if (state.selection instanceof NodeSelection && state.selection.node.isBlock) {
|
||||
if (!$from.parentOffset || !canSplit(state.doc, $from.pos)) return false
|
||||
if (dispatch) dispatch(state.tr.split($from.pos).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
if (!$from.parent.isBlock) return false
|
||||
|
||||
if (dispatch) {
|
||||
const atEnd = $to.parentOffset === $to.parent.content.size
|
||||
const { tr } = state
|
||||
if (state.selection instanceof TextSelection) tr.deleteSelection()
|
||||
const deflt = $from.depth === 0 ? null : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)))
|
||||
let types = atEnd && deflt ? [{ type: deflt, attrs: copyAttributes ? $from.node().attrs : {} }] : null
|
||||
// let types = atEnd && deflt ? [{ type: deflt }] : null
|
||||
// @ts-ignore
|
||||
let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types)
|
||||
// @ts-ignore
|
||||
if (!types && !can && canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt && [{ type: deflt }])) {
|
||||
// @ts-ignore
|
||||
types = [{ type: deflt, attrs: copyAttributes ? $from.node().attrs : {} }]
|
||||
// types = [{ type: deflt }]
|
||||
can = true
|
||||
}
|
||||
if (can) {
|
||||
// @ts-ignore
|
||||
tr.split(tr.mapping.map($from.pos), 1, types)
|
||||
if (!atEnd && !$from.parentOffset && $from.parent.type !== deflt
|
||||
// @ts-ignore
|
||||
&& $from.node(-1).canReplace($from.index(-1), $from.indexAfter(-1), Fragment.from(deflt.create(), $from.parent))) { tr.setNodeMarkup(tr.mapping.map($from.before()), deflt) }
|
||||
}
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SplitBlock: typeof SplitBlock,
|
||||
}
|
||||
}
|
||||
23
packages/core/src/extensions/splitListItem.ts
Normal file
23
packages/core/src/extensions/splitListItem.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { splitListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const SplitListItem = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
splitListItem: (typeOrName: string | NodeType): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return splitListItem(type)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SplitListItem: typeof SplitListItem,
|
||||
}
|
||||
}
|
||||
29
packages/core/src/extensions/toggleBlockType.ts
Normal file
29
packages/core/src/extensions/toggleBlockType.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import nodeIsActive from '../utils/nodeIsActive'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const ToggleBlockType = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
toggleBlockType: (typeOrName: string | NodeType, toggleTypeOrName: string | NodeType, attrs = {}): Command => ({ state, commands }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
const toggleType = getNodeType(toggleTypeOrName, state.schema)
|
||||
const isActive = nodeIsActive(state, type, attrs)
|
||||
|
||||
if (isActive) {
|
||||
return commands.setBlockType(toggleType)
|
||||
}
|
||||
|
||||
return commands.setBlockType(type, attrs)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ToggleBlockType: typeof ToggleBlockType,
|
||||
}
|
||||
}
|
||||
52
packages/core/src/extensions/toggleList.ts
Normal file
52
packages/core/src/extensions/toggleList.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { wrapInList, liftListItem } from 'prosemirror-schema-list'
|
||||
import { findParentNode } from 'prosemirror-utils'
|
||||
import { Node, NodeType, Schema } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
function isList(node: Node, schema: Schema) {
|
||||
return (node.type === schema.nodes.bullet_list
|
||||
|| node.type === schema.nodes.ordered_list
|
||||
|| node.type === schema.nodes.todo_list)
|
||||
}
|
||||
|
||||
export const ToggleList = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
toggleList: (listTypeOrName: string | NodeType, itemTypeOrName: string | NodeType): Command => ({ tr, state, dispatch }) => {
|
||||
const listType = getNodeType(listTypeOrName, state.schema)
|
||||
const itemType = getNodeType(itemTypeOrName, state.schema)
|
||||
const { schema, selection } = state
|
||||
const { $from, $to } = selection
|
||||
const range = $from.blockRange($to)
|
||||
|
||||
if (!range) {
|
||||
return false
|
||||
}
|
||||
|
||||
const parentList = findParentNode(node => isList(node, schema))(selection)
|
||||
|
||||
if (range.depth >= 1 && parentList && range.depth - parentList.depth <= 1) {
|
||||
if (parentList.node.type === listType) {
|
||||
return liftListItem(itemType)(state, dispatch)
|
||||
}
|
||||
|
||||
if (isList(parentList.node, schema) && listType.validContent(parentList.node.content)) {
|
||||
tr.setNodeMarkup(parentList.pos, listType)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return wrapInList(listType)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ToggleList: typeof ToggleList,
|
||||
}
|
||||
}
|
||||
23
packages/core/src/extensions/toggleMark.ts
Normal file
23
packages/core/src/extensions/toggleMark.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { toggleMark as originalToggleMark } from 'prosemirror-commands'
|
||||
import { MarkType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getMarkType from '../utils/getMarkType'
|
||||
|
||||
export const ToggleMark = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
toggleMark: (typeOrName: string | MarkType): Command => ({ state, dispatch }) => {
|
||||
const type = getMarkType(typeOrName, state.schema)
|
||||
|
||||
return originalToggleMark(type)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ToggleMark: typeof ToggleMark,
|
||||
}
|
||||
}
|
||||
29
packages/core/src/extensions/toggleWrap.ts
Normal file
29
packages/core/src/extensions/toggleWrap.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { wrapIn, lift } from 'prosemirror-commands'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import nodeIsActive from '../utils/nodeIsActive'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const ToggleWrap = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
toggleWrap: (typeOrName: string | NodeType, attrs = {}): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
const isActive = nodeIsActive(state, type, attrs)
|
||||
|
||||
if (isActive) {
|
||||
return lift(state, dispatch)
|
||||
}
|
||||
|
||||
return wrapIn(type, attrs)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ToggleWrap: typeof ToggleWrap,
|
||||
}
|
||||
}
|
||||
43
packages/core/src/extensions/updateMark.ts
Normal file
43
packages/core/src/extensions/updateMark.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { MarkType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getMarkType from '../utils/getMarkType'
|
||||
import getMarkRange from '../utils/getMarkRange'
|
||||
|
||||
export const UpdateMark = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
updateMark: (typeOrName: string | MarkType, attrs: {}): Command => ({ tr, state }) => {
|
||||
const { selection, doc } = tr
|
||||
let { from, to } = selection
|
||||
const { $from, empty } = selection
|
||||
const type = getMarkType(typeOrName, state.schema)
|
||||
|
||||
if (empty) {
|
||||
const range = getMarkRange($from, type)
|
||||
|
||||
if (range) {
|
||||
from = range.from
|
||||
to = range.to
|
||||
}
|
||||
}
|
||||
|
||||
const hasMark = doc.rangeHasMark(from, to, type)
|
||||
|
||||
if (hasMark) {
|
||||
tr.removeMark(from, to, type)
|
||||
}
|
||||
|
||||
tr.addMark(from, to, type.create(attrs))
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
UpdateMark: typeof UpdateMark,
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export default function (regexp: RegExp, markType: MarkType, getAttrs?: Function
|
||||
|
||||
if (match[m]) {
|
||||
const matchStart = start + match[0].indexOf(match[m - 1])
|
||||
const matchEnd = matchStart + match[m - 1].length
|
||||
const matchEnd = matchStart + match[m - 1].length - 1
|
||||
const textStart = matchStart + match[m - 1].lastIndexOf(match[m])
|
||||
const textEnd = textStart + match[m].length
|
||||
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
import Extension from './Extension'
|
||||
import Node from './Node'
|
||||
import Mark from './Mark'
|
||||
import { Extension } from './Extension'
|
||||
import { NodeExtension } from './NodeExtension'
|
||||
import { MarkExtension } from './MarkExtension'
|
||||
|
||||
export type Extensions = (Extension | Node | Mark)[]
|
||||
export type Extensions = (Extension | NodeExtension | MarkExtension)[]
|
||||
|
||||
export type Attribute = {
|
||||
default: any,
|
||||
rendered?: boolean,
|
||||
renderHTML?: ((attributes: { [key: string]: any }) => { [key: string]: any } | null) | null,
|
||||
parseHTML?: ((element: HTMLElement) => { [key: string]: any } | null) | null,
|
||||
}
|
||||
|
||||
export type Attributes = {
|
||||
[key: string]: Attribute,
|
||||
}
|
||||
|
||||
export type ExtensionAttribute = {
|
||||
type: string,
|
||||
name: string,
|
||||
attribute: Required<Attribute>,
|
||||
}
|
||||
|
||||
export type GlobalAttributes = {
|
||||
types: string[],
|
||||
attributes: Attributes,
|
||||
}[]
|
||||
|
||||
export type PickValue<T, K extends keyof T> = T[K]
|
||||
|
||||
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I)=>void)
|
||||
? I
|
||||
: never
|
||||
|
||||
export type Diff<T extends keyof any, U extends keyof any> =
|
||||
({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
|
||||
|
||||
export type Overwrite<T, U> = Pick<T, Diff<keyof T, keyof U>> & U;
|
||||
|
||||
export type AnyObject = {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
72
packages/core/src/utils/getAttributesFromExtensions.ts
Normal file
72
packages/core/src/utils/getAttributesFromExtensions.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import splitExtensions from './splitExtensions'
|
||||
import {
|
||||
Extensions,
|
||||
GlobalAttributes,
|
||||
Attributes,
|
||||
Attribute,
|
||||
ExtensionAttribute,
|
||||
} from '../types'
|
||||
|
||||
/**
|
||||
* Get a list of all extension attributes defined in `addAttribute` and `addGlobalAttribute`.
|
||||
* @param extensions List of extensions
|
||||
*/
|
||||
export default function getAttributesFromExtensions(extensions: Extensions) {
|
||||
const extensionAttributes: ExtensionAttribute[] = []
|
||||
const { nodeExtensions, markExtensions } = splitExtensions(extensions)
|
||||
const nodeAndMarkExtensions = [...nodeExtensions, ...markExtensions]
|
||||
const defaultAttribute: Required<Attribute> = {
|
||||
default: null,
|
||||
rendered: true,
|
||||
renderHTML: null,
|
||||
parseHTML: null,
|
||||
}
|
||||
|
||||
extensions.forEach(extension => {
|
||||
const context = {
|
||||
options: extension.options,
|
||||
}
|
||||
|
||||
const globalAttributes = extension.addGlobalAttributes.bind(context)() as GlobalAttributes
|
||||
|
||||
globalAttributes.forEach(globalAttribute => {
|
||||
globalAttribute.types.forEach(type => {
|
||||
Object
|
||||
.entries(globalAttribute.attributes)
|
||||
.forEach(([name, attribute]) => {
|
||||
extensionAttributes.push({
|
||||
type,
|
||||
name,
|
||||
attribute: {
|
||||
...defaultAttribute,
|
||||
...attribute,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
nodeAndMarkExtensions.forEach(extension => {
|
||||
const context = {
|
||||
options: extension.options,
|
||||
}
|
||||
|
||||
const attributes = extension.addAttributes.bind(context)() as Attributes
|
||||
|
||||
Object
|
||||
.entries(attributes)
|
||||
.forEach(([name, attribute]) => {
|
||||
extensionAttributes.push({
|
||||
type: extension.name,
|
||||
name,
|
||||
attribute: {
|
||||
...defaultAttribute,
|
||||
...attribute,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return extensionAttributes
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import collect from 'collect.js'
|
||||
import Mark from '../Mark'
|
||||
import { Extensions } from '../types'
|
||||
|
||||
export default function getMarksFromExtensions(extensions: Extensions): any {
|
||||
return collect(extensions)
|
||||
.where('type', 'mark')
|
||||
.mapWithKeys((extension: Mark) => [extension.config.name, extension.config.schema])
|
||||
.all()
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import collect from 'collect.js'
|
||||
import Node from '../Node'
|
||||
import { Extensions } from '../types'
|
||||
|
||||
export default function getNodesFromExtensions(extensions: Extensions): any {
|
||||
return collect(extensions)
|
||||
.where('type', 'node')
|
||||
.mapWithKeys((extension: Node) => [extension.config.name, extension.config.schema])
|
||||
.all()
|
||||
}
|
||||
20
packages/core/src/utils/getRenderedAttributes.ts
Normal file
20
packages/core/src/utils/getRenderedAttributes.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Node, Mark } from 'prosemirror-model'
|
||||
import { ExtensionAttribute } from '../types'
|
||||
import mergeAttributes from './mergeAttributes'
|
||||
|
||||
export default function getRenderedAttributes(nodeOrMark: Node | Mark, extensionAttributes: ExtensionAttribute[]): { [key: string]: any } {
|
||||
return extensionAttributes
|
||||
.filter(item => item.attribute.rendered)
|
||||
.map(item => {
|
||||
if (!item.attribute.renderHTML) {
|
||||
return {
|
||||
[item.name]: nodeOrMark.attrs[item.name],
|
||||
}
|
||||
}
|
||||
|
||||
return item.attribute.renderHTML(nodeOrMark.attrs) || {}
|
||||
})
|
||||
.reduce((attributes, attribute) => {
|
||||
return mergeAttributes(attributes, attribute)
|
||||
}, {})
|
||||
}
|
||||
@@ -1,26 +1,93 @@
|
||||
import deepmerge from 'deepmerge'
|
||||
import { Schema } from 'prosemirror-model'
|
||||
import { NodeSpec, MarkSpec, Schema } from 'prosemirror-model'
|
||||
import { Extensions } from '../types'
|
||||
import getTopNodeFromExtensions from './getTopNodeFromExtensions'
|
||||
import getNodesFromExtensions from './getNodesFromExtensions'
|
||||
import getMarksFromExtensions from './getMarksFromExtensions'
|
||||
import resolveExtensionConfig from './resolveExtensionConfig'
|
||||
import splitExtensions from './splitExtensions'
|
||||
import getAttributesFromExtensions from './getAttributesFromExtensions'
|
||||
import getRenderedAttributes from './getRenderedAttributes'
|
||||
import isEmptyObject from './isEmptyObject'
|
||||
import injectExtensionAttributesToParseRule from './injectExtensionAttributesToParseRule'
|
||||
|
||||
function cleanUpSchemaItem<T>(data: T) {
|
||||
return Object.fromEntries(Object.entries(data).filter(([key, value]) => {
|
||||
if (key === 'attrs' && isEmptyObject(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return value !== null && value !== undefined
|
||||
})) as T
|
||||
}
|
||||
|
||||
export default function getSchema(extensions: Extensions): Schema {
|
||||
extensions.forEach(extension => {
|
||||
resolveExtensionConfig(extension, 'name')
|
||||
resolveExtensionConfig(extension, 'defaults')
|
||||
resolveExtensionConfig(extension, 'topNode')
|
||||
const allAttributes = getAttributesFromExtensions(extensions)
|
||||
const { nodeExtensions, markExtensions } = splitExtensions(extensions)
|
||||
const topNode = nodeExtensions.find(extension => extension.topNode)?.name
|
||||
|
||||
const { name } = extension.config
|
||||
const options = deepmerge(extension.config.defaults, extension.options)
|
||||
const nodes = Object.fromEntries(nodeExtensions.map(extension => {
|
||||
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name)
|
||||
const context = { options: extension.options }
|
||||
const schema: NodeSpec = cleanUpSchemaItem({
|
||||
content: extension.content,
|
||||
marks: extension.marks,
|
||||
group: extension.group,
|
||||
inline: extension.inline,
|
||||
atom: extension.atom,
|
||||
selectable: extension.selectable,
|
||||
draggable: extension.draggable,
|
||||
code: extension.code,
|
||||
defining: extension.defining,
|
||||
isolating: extension.isolating,
|
||||
attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => {
|
||||
return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }]
|
||||
})),
|
||||
})
|
||||
|
||||
resolveExtensionConfig(extension, 'schema', { name, options })
|
||||
})
|
||||
if (extension.parseHTML) {
|
||||
schema.parseDOM = extension.parseHTML
|
||||
.bind(context)()
|
||||
?.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes))
|
||||
}
|
||||
|
||||
if (extension.renderHTML) {
|
||||
schema.toDOM = node => (extension.renderHTML as Function)?.bind(context)({
|
||||
node,
|
||||
attributes: getRenderedAttributes(node, extensionAttributes),
|
||||
})
|
||||
}
|
||||
|
||||
return [extension.name, schema]
|
||||
}))
|
||||
|
||||
const marks = Object.fromEntries(markExtensions.map(extension => {
|
||||
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name)
|
||||
const context = { options: extension.options }
|
||||
const schema: MarkSpec = cleanUpSchemaItem({
|
||||
inclusive: extension.inclusive,
|
||||
excludes: extension.excludes,
|
||||
group: extension.group,
|
||||
spanning: extension.spanning,
|
||||
attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => {
|
||||
return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }]
|
||||
})),
|
||||
})
|
||||
|
||||
if (extension.parseHTML) {
|
||||
schema.parseDOM = extension.parseHTML
|
||||
.bind(context)()
|
||||
?.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes))
|
||||
}
|
||||
|
||||
if (extension.renderHTML) {
|
||||
schema.toDOM = mark => (extension.renderHTML as Function)?.bind(context)({
|
||||
mark,
|
||||
attributes: getRenderedAttributes(mark, extensionAttributes),
|
||||
})
|
||||
}
|
||||
|
||||
return [extension.name, schema]
|
||||
}))
|
||||
|
||||
return new Schema({
|
||||
topNode: getTopNodeFromExtensions(extensions),
|
||||
nodes: getNodesFromExtensions(extensions),
|
||||
marks: getMarksFromExtensions(extensions),
|
||||
topNode,
|
||||
nodes,
|
||||
marks,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ import { Schema } from 'prosemirror-model'
|
||||
|
||||
export default function getSchemaTypeByName(name: string, schema: Schema) {
|
||||
if (schema.nodes[name]) {
|
||||
return 'node'
|
||||
return schema.nodes[name]
|
||||
}
|
||||
|
||||
if (schema.marks[name]) {
|
||||
return 'mark'
|
||||
return schema.marks[name]
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
13
packages/core/src/utils/getSchemaTypeNameByName.ts
Normal file
13
packages/core/src/utils/getSchemaTypeNameByName.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Schema } from 'prosemirror-model'
|
||||
|
||||
export default function getSchemaTypeNameByName(name: string, schema: Schema) {
|
||||
if (schema.nodes[name]) {
|
||||
return 'node'
|
||||
}
|
||||
|
||||
if (schema.marks[name]) {
|
||||
return 'mark'
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import collect from 'collect.js'
|
||||
import { Extensions } from '../types'
|
||||
|
||||
export default function getTopNodeFromExtensions(extensions: Extensions): any {
|
||||
const topNode = collect(extensions).firstWhere('config.topNode', true)
|
||||
|
||||
if (topNode) {
|
||||
return topNode.config.name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ParseRule } from 'prosemirror-model'
|
||||
import { ExtensionAttribute } from '../types'
|
||||
|
||||
/**
|
||||
* This function merges extension attributes into parserule attributes (`attrs` or `getAttrs`).
|
||||
* Cancels when `getAttrs` returned `false`.
|
||||
* @param parseRule ProseMirror ParseRule
|
||||
* @param extensionAttributes List of attributes to inject
|
||||
*/
|
||||
export default function injectExtensionAttributesToParseRule(parseRule: ParseRule, extensionAttributes: ExtensionAttribute[]): ParseRule {
|
||||
if (parseRule.style) {
|
||||
return parseRule
|
||||
}
|
||||
|
||||
return {
|
||||
...parseRule,
|
||||
getAttrs: node => {
|
||||
const oldAttributes = parseRule.getAttrs
|
||||
? parseRule.getAttrs(node)
|
||||
: parseRule.attrs
|
||||
|
||||
if (oldAttributes === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
const newAttributes = extensionAttributes
|
||||
.filter(item => item.attribute.rendered)
|
||||
.reduce((items, item) => {
|
||||
const attributes = item.attribute.parseHTML
|
||||
? item.attribute.parseHTML(node as HTMLElement) || {}
|
||||
: {
|
||||
[item.name]: (node as HTMLElement).getAttribute(item.name),
|
||||
}
|
||||
|
||||
const filteredAttributes = Object.fromEntries(Object.entries(attributes)
|
||||
.filter(([, value]) => value !== undefined && value !== null))
|
||||
|
||||
return {
|
||||
...items,
|
||||
...filteredAttributes,
|
||||
}
|
||||
}, {})
|
||||
|
||||
return { ...oldAttributes, ...newAttributes }
|
||||
},
|
||||
}
|
||||
}
|
||||
3
packages/core/src/utils/isEmptyObject.ts
Normal file
3
packages/core/src/utils/isEmptyObject.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function isEmptyObject(object = {}) {
|
||||
return Object.keys(object).length === 0 && object.constructor === Object
|
||||
}
|
||||
25
packages/core/src/utils/mergeAttributes.ts
Normal file
25
packages/core/src/utils/mergeAttributes.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { AnyObject } from '../types'
|
||||
|
||||
export default function mergeAttributes(...object: AnyObject[]) {
|
||||
return object.reduce((items, item) => {
|
||||
const mergedAttributes = { ...items }
|
||||
|
||||
Object.entries(item).forEach(([key, value]) => {
|
||||
if (!mergedAttributes[key]) {
|
||||
mergedAttributes[key] = value
|
||||
return
|
||||
}
|
||||
|
||||
if (key === 'class') {
|
||||
mergedAttributes[key] = [mergedAttributes[key], value].join(' ')
|
||||
return
|
||||
}
|
||||
|
||||
if (key === 'style') {
|
||||
mergedAttributes[key] = [mergedAttributes[key], value].join('; ')
|
||||
}
|
||||
})
|
||||
|
||||
return mergedAttributes
|
||||
}, {})
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import deepmerge from 'deepmerge'
|
||||
import Extension from '../Extension'
|
||||
import Node from '../Node'
|
||||
import Mark from '../Mark'
|
||||
|
||||
export default function resolveExtensionConfig(
|
||||
extension: Extension | Node | Mark,
|
||||
name: string,
|
||||
props = {},
|
||||
): void {
|
||||
if (!extension.configs[name]) {
|
||||
return
|
||||
}
|
||||
|
||||
extension.config[name] = extension.configs[name]
|
||||
.reduce((accumulator, { stategy, value: rawValue }) => {
|
||||
const value = typeof rawValue === 'function'
|
||||
? rawValue(props)
|
||||
: rawValue
|
||||
|
||||
if (accumulator === undefined) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (stategy === 'overwrite') {
|
||||
return value
|
||||
}
|
||||
|
||||
if (stategy === 'extend') {
|
||||
return deepmerge(accumulator, value)
|
||||
}
|
||||
|
||||
return accumulator
|
||||
}, undefined)
|
||||
}
|
||||
16
packages/core/src/utils/splitExtensions.ts
Normal file
16
packages/core/src/utils/splitExtensions.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Extensions } from '../types'
|
||||
import { Extension } from '../Extension'
|
||||
import { NodeExtension } from '../NodeExtension'
|
||||
import { MarkExtension } from '../MarkExtension'
|
||||
|
||||
export default function splitExtensions(extensions: Extensions) {
|
||||
const baseExtensions = extensions.filter(extension => extension.type === 'extension') as Extension[]
|
||||
const nodeExtensions = extensions.filter(extension => extension.type === 'node') as NodeExtension[]
|
||||
const markExtensions = extensions.filter(extension => extension.type === 'mark') as MarkExtension[]
|
||||
|
||||
return {
|
||||
baseExtensions,
|
||||
nodeExtensions,
|
||||
markExtensions,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user