Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@bangle.dev/core

Package Overview
Dependencies
Maintainers
1
Versions
143
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bangle.dev/core - npm Package Compare versions

Comparing version 0.28.11 to 0.29.0-alpha.0

dist/index.cjs

6

__tests__/dom-serialization-helpers.test.ts
/**
* @jest-environment jsdom
*/
import { criticalComponents } from '../index';
import { RawSpecs, SpecRegistry } from '../spec-registry';
import { domSerializationHelpers } from '../dom-serialization-helpers';
import { criticalComponents } from '../src/index';
import { RawSpecs, SpecRegistry } from '../src/spec-registry';
import { domSerializationHelpers } from '../src/dom-serialization-helpers';

@@ -8,0 +8,0 @@ const { paragraph, doc, text } = criticalComponents;

@@ -1,7 +0,7 @@

import { criticalComponents } from '../index';
import { criticalComponents } from '../src/index';
import { Plugin, PluginKey } from '@bangle.dev/pm';
import { RawSpecs, SpecRegistry } from '../spec-registry';
import { NodeView } from '../node-view';
import { pluginLoader, RawPlugins } from '../plugin-loader';
import { PluginGroup } from '../plugin';
import { RawSpecs, SpecRegistry } from '../src/spec-registry';
import { NodeView } from '../src/node-view';
import { pluginLoader, RawPlugins } from '../src/plugin-loader';
import { PluginGroup } from '../src/plugin-group';

@@ -8,0 +8,0 @@ const { paragraph } = criticalComponents;

@@ -1,15 +0,355 @@

import * as doc from './critical-components/doc';
import * as paragraph from './critical-components/paragraph';
import * as text from './critical-components/text';
import * as history from './critical-components/history';
import * as editorStateCounter from './critical-components/editor-state-counter';
export * from './bangle-editor';
export * from './bangle-editor-state';
export * from './create-element';
export * from './node-view';
export * from './plugin';
export * from './spec-registry';
export * from './dom-serialization-helpers';
export type { RawPlugins } from './plugin-loader';
export declare const criticalComponents: {
import * as pmHistory from '@bangle.dev/pm';
import { NodeSpec, Node, MarkSpec, Mark, Schema, Plugin, InputRule, Command, EditorState, PluginKey, EditorProps, Selection, dropCursor, EditorView, DirectEditorProps, DOMOutputSpec, Decoration } from '@bangle.dev/pm';
export { Plugin, PluginKey } from '@bangle.dev/pm';
import { MarkdownSerializerState } from 'prosemirror-markdown';
import * as prosemirror_model from 'prosemirror-model';
declare type PMSpec = NodeSpec | MarkSpec;
interface BaseRawNodeSpec {
name: string;
type: 'node';
topNode?: boolean;
schema: NodeSpec;
markdown?: {
toMarkdown: (state: MarkdownSerializerState, node: Node, parent: Node, index: number) => void;
parseMarkdown?: {
[key: string]: any;
};
};
options?: {
[k: string]: any;
};
}
interface BaseRawMarkSpec {
name: string;
type: 'mark';
schema: MarkSpec;
markdown?: {
toMarkdown: {
open: string | ((_state: MarkdownSerializerState, mark: Mark, parent: Node, index: number) => void);
close: string | ((_state: MarkdownSerializerState, mark: Mark, parent: Node, index: number) => void);
mixable?: boolean;
escape?: boolean;
expelEnclosingWhitespace?: boolean;
};
parseMarkdown?: {
[k: string]: any;
};
};
options?: {
[k: string]: any;
};
}
declare type RawSpecs = null | false | undefined | BaseRawNodeSpec | BaseRawMarkSpec | RawSpecs[];
declare class SpecRegistry<N extends string = any, M extends string = any> {
_spec: PMSpec[];
_schema: Schema<N, M>;
_options: {
[key: string]: any;
};
constructor(rawSpecs?: RawSpecs, { defaultSpecs }?: {
defaultSpecs?: boolean | undefined;
});
get spec(): PMSpec[];
get schema(): Schema<N, M>;
get options(): {
[key: string]: any;
};
}
declare const spec$2: typeof specFactory$2;
declare function specFactory$2({ content }?: {
content?: string | undefined;
}): BaseRawNodeSpec;
declare namespace doc {
export {
spec$2 as spec,
};
}
interface DeepPluginArray extends Array<Plugin | DeepPluginArray> {
}
declare class PluginGroup {
name: string;
plugins: DeepPluginArray;
constructor(name: string, plugins: DeepPluginArray);
}
interface PluginPayload<T = any> {
schema: Schema;
specRegistry: SpecRegistry;
metadata: T;
}
declare type BaseRawPlugins = false | null | Plugin | InputRule | PluginGroup | BaseRawPlugins[];
declare type RawPlugins<T = any> = BaseRawPlugins | ((payLoad: PluginPayload<T>) => BaseRawPlugins);
declare const spec$1: typeof specFactory$1;
declare const plugins$2: typeof pluginsFactory$2;
declare const commands$1: {
convertToParagraph: typeof convertToParagraph;
jumpToStartOfParagraph: typeof jumpToStartOfParagraph;
jumpToEndOfParagraph: typeof jumpToEndOfParagraph;
queryIsParagraph: typeof queryIsParagraph;
queryIsTopLevelParagraph: typeof queryIsTopLevelParagraph;
insertEmptyParagraphAbove: typeof insertEmptyParagraphAbove;
insertEmptyParagraphBelow: typeof insertEmptyParagraphBelow;
};
declare const defaultKeys$1: {
jumpToEndOfParagraph: string;
jumpToStartOfParagraph: string;
moveDown: string;
moveUp: string;
emptyCopy: string;
emptyCut: string;
insertEmptyParaAbove: string;
insertEmptyParaBelow: string;
convertToParagraph: string;
};
declare function specFactory$1(): BaseRawNodeSpec;
declare function pluginsFactory$2({ keybindings }?: {
keybindings?: {
jumpToEndOfParagraph: string;
jumpToStartOfParagraph: string;
moveDown: string;
moveUp: string;
emptyCopy: string;
emptyCut: string;
insertEmptyParaAbove: string;
insertEmptyParaBelow: string;
convertToParagraph: string;
} | undefined;
}): RawPlugins;
declare function convertToParagraph(): Command;
declare function queryIsTopLevelParagraph(): (state: EditorState) => boolean;
declare function queryIsParagraph(): (state: EditorState) => boolean;
declare function insertEmptyParagraphAbove(): Command;
declare function insertEmptyParagraphBelow(): Command;
declare function jumpToStartOfParagraph(): Command;
declare function jumpToEndOfParagraph(): Command;
declare const paragraph_convertToParagraph: typeof convertToParagraph;
declare const paragraph_queryIsTopLevelParagraph: typeof queryIsTopLevelParagraph;
declare const paragraph_queryIsParagraph: typeof queryIsParagraph;
declare const paragraph_insertEmptyParagraphAbove: typeof insertEmptyParagraphAbove;
declare const paragraph_insertEmptyParagraphBelow: typeof insertEmptyParagraphBelow;
declare const paragraph_jumpToStartOfParagraph: typeof jumpToStartOfParagraph;
declare const paragraph_jumpToEndOfParagraph: typeof jumpToEndOfParagraph;
declare namespace paragraph {
export {
spec$1 as spec,
plugins$2 as plugins,
commands$1 as commands,
defaultKeys$1 as defaultKeys,
paragraph_convertToParagraph as convertToParagraph,
paragraph_queryIsTopLevelParagraph as queryIsTopLevelParagraph,
paragraph_queryIsParagraph as queryIsParagraph,
paragraph_insertEmptyParagraphAbove as insertEmptyParagraphAbove,
paragraph_insertEmptyParagraphBelow as insertEmptyParagraphBelow,
paragraph_jumpToStartOfParagraph as jumpToStartOfParagraph,
paragraph_jumpToEndOfParagraph as jumpToEndOfParagraph,
};
}
declare const spec: typeof specFactory;
declare function specFactory(): BaseRawNodeSpec;
declare const text_spec: typeof spec;
declare namespace text {
export {
text_spec as spec,
};
}
declare const plugins$1: typeof pluginsFactory$1;
declare const commands: {
undo: typeof undo;
redo: typeof redo;
};
declare const defaultKeys: {
undo: string;
redo: string;
redoAlt: string;
};
declare function pluginsFactory$1({ historyOpts, keybindings, }?: {
historyOpts?: {} | undefined;
keybindings?: {
undo: string;
redo: string;
redoAlt: string;
} | undefined;
}): RawPlugins;
declare function undo(): pmHistory.Command;
declare function redo(): pmHistory.Command;
declare const history_commands: typeof commands;
declare const history_defaultKeys: typeof defaultKeys;
declare const history_undo: typeof undo;
declare const history_redo: typeof redo;
declare namespace history {
export {
plugins$1 as plugins,
history_commands as commands,
history_defaultKeys as defaultKeys,
history_undo as undo,
history_redo as redo,
};
}
declare const plugins: typeof pluginsFactory;
declare const docChangedKey: PluginKey<any>;
declare const selectionChangedKey: PluginKey<any>;
declare function pluginsFactory(): RawPlugins;
declare const editorStateCounter_plugins: typeof plugins;
declare const editorStateCounter_docChangedKey: typeof docChangedKey;
declare const editorStateCounter_selectionChangedKey: typeof selectionChangedKey;
declare namespace editorStateCounter {
export {
editorStateCounter_plugins as plugins,
editorStateCounter_docChangedKey as docChangedKey,
editorStateCounter_selectionChangedKey as selectionChangedKey,
};
}
declare type InitialContent = string | Node | object;
interface BangleEditorStateProps<PluginMetadata = any> {
specRegistry?: SpecRegistry;
specs?: RawSpecs;
plugins?: RawPlugins;
initialValue?: InitialContent;
editorProps?: EditorProps;
pmStateOpts?: {
selection?: Selection | undefined;
storedMarks?: Mark[] | null | undefined;
};
pluginMetadata?: PluginMetadata;
dropCursorOpts?: Parameters<typeof dropCursor>[0];
}
declare class BangleEditorState<PluginMetadata> {
specRegistry: SpecRegistry;
pmState: EditorState;
constructor({ specRegistry, specs, plugins, initialValue, editorProps, pmStateOpts, pluginMetadata, dropCursorOpts, }?: BangleEditorStateProps<PluginMetadata>);
}
declare type PMViewOpts = Omit<DirectEditorProps, 'state' | 'dispatchTransaction' | 'attributes'>;
interface BangleEditorProps<PluginMetadata> {
focusOnInit?: boolean;
state: BangleEditorState<PluginMetadata>;
pmViewOpts?: PMViewOpts;
}
declare class BangleEditor<PluginMetadata = any> {
destroyed: boolean;
view: EditorView;
constructor(element: HTMLElement, { focusOnInit, state, pmViewOpts, }: BangleEditorProps<PluginMetadata>);
focusView(): void;
destroy(): void;
toHTMLString(): string;
}
declare function createElement(spec: DOMOutputSpec): HTMLElement;
declare type GetPosFunction = () => number;
declare type UpdateAttrsFunction = (attrs: Node['attrs']) => void;
interface NodeViewProps {
node: Node;
view: EditorView;
getPos: GetPosFunction;
decorations: readonly Decoration[];
selected: boolean;
attrs: Node['attrs'];
updateAttrs: UpdateAttrsFunction;
}
declare type RenderHandlerFunction = (nodeView: NodeView, props: NodeViewProps) => void;
interface RenderHandlers {
create: RenderHandlerFunction;
update: RenderHandlerFunction;
destroy: RenderHandlerFunction;
}
declare abstract class BaseNodeView {
_node: Node;
_view: EditorView;
_getPos: () => number;
_decorations: readonly Decoration[];
_selected: boolean;
contentDOM?: HTMLElement;
containerDOM?: HTMLElement;
renderHandlers: RenderHandlers;
opts: {
selectionSensitive: boolean;
};
getAttrs(): prosemirror_model.Attrs;
getNodeViewProps(): NodeViewProps;
get dom(): InstanceType<typeof window.Node>;
constructor({ node, view, getPos, decorations, containerDOM, contentDOM, renderHandlers, }: {
node: Node;
view: EditorView;
getPos: () => number;
decorations: readonly Decoration[];
contentDOM?: HTMLElement;
containerDOM?: HTMLElement;
renderHandlers?: RenderHandlers;
}, { selectionSensitive }?: {
selectionSensitive?: boolean | undefined;
});
}
declare class NodeView extends BaseNodeView {
/**
* The idea here is to figure out whether your component
* will be hole-y (will let pm put in contents) or be opaque (example emoji).
* NOTE: if passing contentDOM, it is your responsibility to insert it into
* containerDOM.
* NOTE: when dealing with renderHandlers like .create or .update
* donot assume anything about the current state of dom elements. For
* example, the dom you created in .create handler, may or may not exist,
* when the .update is called.
*
*/
static createPlugin({ name, containerDOM: containerDOMSpec, contentDOM: contentDOMSpec, // only for components which need to have editable content
renderHandlers, }: {
name: string;
contentDOM?: DOMOutputSpec;
containerDOM: DOMOutputSpec;
renderHandlers?: RenderHandlers;
}): Plugin<any>;
update(node: Node, decorations: readonly Decoration[]): boolean;
selectNode(): void;
deselectNode(): void;
ignoreMutation(mutation: MutationRecord | {
type: 'selection';
target: Element;
}): boolean;
destroy(): void;
}
declare function saveRenderHandlers(editorContainer: HTMLElement, handlers: RenderHandlers): void;
declare function getRenderHandlers(view: EditorView): RenderHandlers | undefined;
/**
* Creates a bare bone `toDOM` and `parseDOM` handlers for the PM schema.
* The use case is for nodes or marks who already have a nodeView
* and want to get basic `toDOM`, `parseDOM` to enable drag n drop or
* copy paste.
*
* @param {*} spec
* @param {Object} opts
* @param {string} opts.tag
* @param {string|0|(node)=>string} opts.content - `0` signals content that PM will inject.
* @param {string} opts.ignoreAttrs
* @param {Number} opts.parsingPriority https://prosemirror.net/docs/ref/#model.ParseRule.priority
*/
declare function domSerializationHelpers(name: string, { tag, content, ignoreAttrs, parsingPriority, }?: {
tag?: string;
content?: DOMOutputSpec | ((node: Node) => DOMOutputSpec);
ignoreAttrs?: string[];
parsingPriority?: number;
}): {
toDOM: (node: Node) => DOMOutputSpec;
parseDOM: {
priority: number;
tag: string;
getAttrs: (dom: any) => any;
}[];
};
declare const criticalComponents: {
doc: typeof doc;

@@ -21,2 +361,3 @@ paragraph: typeof paragraph;

};
//# sourceMappingURL=index.d.ts.map
export { BangleEditor, BangleEditorProps, BangleEditorState, BangleEditorStateProps, BaseRawMarkSpec, BaseRawNodeSpec, NodeView, NodeViewProps, RawPlugins, RawSpecs, RenderHandlerFunction, RenderHandlers, SpecRegistry, UpdateAttrsFunction, createElement, criticalComponents, doc, domSerializationHelpers, editorStateCounter, getRenderHandlers, history, paragraph, saveRenderHandlers, text };

@@ -0,15 +1,791 @@

import * as pmHistory from '@bangle.dev/pm';
import { keymap, setBlockType, PluginKey, Plugin, Schema, baseKeymap, dropCursor, gapCursor, InputRule, inputRules, undoInputRule, EditorState, Node, DOMParser, EditorView, DOMSerializer } from '@bangle.dev/pm';
export { Plugin, PluginKey } from '@bangle.dev/pm';
import { browser, createObject, filter, insertEmpty, findParentNodeOfType, bangleWarn, isTestEnv, toHTMLString, objectFilter } from '@bangle.dev/utils';
import { parentHasDirectParentOfType, moveNode, jumpToStartOfNode, jumpToEndOfNode, copyEmptyCommand, cutEmptyCommand } from '@bangle.dev/pm-commands';
const spec$2 = specFactory$2;
const name$4 = 'doc';
function specFactory$2({ content = 'block+' } = {}) {
return {
type: 'node',
topNode: true,
name: name$4,
schema: {
content: content,
},
};
}
var doc = /*#__PURE__*/Object.freeze({
__proto__: null,
spec: spec$2
});
const spec$1 = specFactory$1;
const plugins$2 = pluginsFactory$2;
const commands$1 = {
convertToParagraph,
jumpToStartOfParagraph,
jumpToEndOfParagraph,
queryIsParagraph,
queryIsTopLevelParagraph,
insertEmptyParagraphAbove,
insertEmptyParagraphBelow,
};
const defaultKeys$1 = {
jumpToEndOfParagraph: browser.mac ? 'Ctrl-e' : 'Ctrl-End',
jumpToStartOfParagraph: browser.mac ? 'Ctrl-a' : 'Ctrl-Home',
moveDown: 'Alt-ArrowDown',
moveUp: 'Alt-ArrowUp',
emptyCopy: 'Mod-c',
emptyCut: 'Mod-x',
insertEmptyParaAbove: 'Mod-Shift-Enter',
insertEmptyParaBelow: 'Mod-Enter',
convertToParagraph: 'Ctrl-Shift-0',
};
const name$3 = 'paragraph';
const getTypeFromSchema = (schema) => schema.nodes[name$3];
function specFactory$1() {
return {
type: 'node',
name: name$3,
schema: {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [
{
tag: 'p',
},
],
toDOM: () => ['p', 0],
},
markdown: {
toMarkdown(state, node) {
state.renderInline(node);
state.closeBlock(node);
},
parseMarkdown: {
paragraph: {
block: 'paragraph',
},
},
},
};
}
function pluginsFactory$2({ keybindings = defaultKeys$1 } = {}) {
return ({ schema }) => {
const type = getTypeFromSchema(schema);
// Enables certain command to only work if paragraph is direct child of the `doc` node
const isTopLevel = parentHasDirectParentOfType(type, schema.nodes.doc);
return [
keybindings &&
keymap(createObject([
[keybindings.convertToParagraph, convertToParagraph()],
[keybindings.moveUp, filter(isTopLevel, moveNode(type, 'UP'))],
[keybindings.moveDown, filter(isTopLevel, moveNode(type, 'DOWN'))],
[keybindings.jumpToStartOfParagraph, jumpToStartOfNode(type)],
[keybindings.jumpToEndOfParagraph, jumpToEndOfNode(type)],
[keybindings.emptyCopy, filter(isTopLevel, copyEmptyCommand(type))],
[keybindings.emptyCut, filter(isTopLevel, cutEmptyCommand(type))],
[
keybindings.insertEmptyParaAbove,
filter(isTopLevel, insertEmpty(type, 'above')),
],
[
keybindings.insertEmptyParaBelow,
filter(isTopLevel, insertEmpty(type, 'below')),
],
])),
];
};
}
// Commands
function convertToParagraph() {
return (state, dispatch) => setBlockType(getTypeFromSchema(state.schema))(state, dispatch);
}
function queryIsTopLevelParagraph() {
return (state) => {
const type = getTypeFromSchema(state.schema);
return parentHasDirectParentOfType(type, state.schema.nodes.doc)(state);
};
}
function queryIsParagraph() {
return (state) => {
const type = getTypeFromSchema(state.schema);
return Boolean(findParentNodeOfType(type)(state.selection));
};
}
function insertEmptyParagraphAbove() {
return (state, dispatch, view) => {
const type = getTypeFromSchema(state.schema);
return filter(parentHasDirectParentOfType(type, state.schema.nodes.doc), insertEmpty(type, 'above'))(state, dispatch, view);
};
}
function insertEmptyParagraphBelow() {
return (state, dispatch, view) => {
const type = getTypeFromSchema(state.schema);
return filter(parentHasDirectParentOfType(type, state.schema.nodes.doc), insertEmpty(type, 'below'))(state, dispatch, view);
};
}
function jumpToStartOfParagraph() {
return (state, dispatch) => {
const type = getTypeFromSchema(state.schema);
return jumpToStartOfNode(type)(state, dispatch);
};
}
function jumpToEndOfParagraph() {
return (state, dispatch) => {
const type = getTypeFromSchema(state.schema);
return jumpToEndOfNode(type)(state, dispatch);
};
}
var paragraph = /*#__PURE__*/Object.freeze({
__proto__: null,
spec: spec$1,
plugins: plugins$2,
commands: commands$1,
defaultKeys: defaultKeys$1,
convertToParagraph: convertToParagraph,
queryIsTopLevelParagraph: queryIsTopLevelParagraph,
queryIsParagraph: queryIsParagraph,
insertEmptyParagraphAbove: insertEmptyParagraphAbove,
insertEmptyParagraphBelow: insertEmptyParagraphBelow,
jumpToStartOfParagraph: jumpToStartOfParagraph,
jumpToEndOfParagraph: jumpToEndOfParagraph
});
const spec = specFactory;
const name$2 = 'text';
function specFactory() {
return {
type: 'node',
name: name$2,
schema: {
group: 'inline',
},
markdown: {
toMarkdown(state, node) {
state.text(node.text);
},
},
};
}
var text = /*#__PURE__*/Object.freeze({
__proto__: null,
spec: spec
});
class PluginGroup {
constructor(name, plugins) {
this.name = name;
this.plugins = plugins;
}
}
const plugins$1 = pluginsFactory$1;
const commands = {
undo,
redo,
};
const defaultKeys = {
undo: 'Mod-z',
redo: 'Mod-y',
redoAlt: 'Shift-Mod-z',
};
const name$1 = 'history';
function pluginsFactory$1({ historyOpts = {}, keybindings = defaultKeys, } = {}) {
return () => {
return new PluginGroup(name$1, [
pmHistory.history(historyOpts),
keybindings &&
keymap(createObject([
[keybindings.undo, undo()],
[keybindings.redo, redo()],
[keybindings.redoAlt, redo()],
])),
]);
};
}
function undo() {
return pmHistory.undo;
}
function redo() {
return pmHistory.redo;
}
var history = /*#__PURE__*/Object.freeze({
__proto__: null,
plugins: plugins$1,
commands: commands,
defaultKeys: defaultKeys,
undo: undo,
redo: redo
});
const name = 'editorStateCounter';
const plugins = pluginsFactory;
const docChangedKey = new PluginKey(name);
const selectionChangedKey = new PluginKey(name);
function pluginsFactory() {
return () => {
return new PluginGroup(name, [
new Plugin({
key: docChangedKey,
state: {
init(_, _state) {
return 0;
},
apply(tr, pluginState, _oldState, _newState) {
return tr.docChanged ? pluginState + 1 : pluginState;
},
},
}),
new Plugin({
key: selectionChangedKey,
state: {
init(_, _state) {
return 0;
},
apply(_tr, pluginState, oldState, newState) {
return newState.selection.eq(oldState && oldState.selection)
? pluginState
: pluginState + 1;
},
},
}),
]);
};
}
var editorStateCounter = /*#__PURE__*/Object.freeze({
__proto__: null,
plugins: plugins,
docChangedKey: docChangedKey,
selectionChangedKey: selectionChangedKey
});
class SpecRegistry {
constructor(rawSpecs = [], { defaultSpecs = true } = {}) {
let flattenedSpecs = flatten$1(rawSpecs);
flattenedSpecs.forEach(validateSpec);
const names = new Set(flattenedSpecs.map((r) => r.name));
if (flattenedSpecs.length !== names.size) {
bangleWarn('The specRegistry has one or more specs with the same name', flattenedSpecs);
throw new Error('Duplicate spec error, please check your specRegistry');
}
if (defaultSpecs) {
const defaultSpecsArray = [];
if (!names.has('paragraph')) {
defaultSpecsArray.unshift(spec$1());
}
if (!names.has('text')) {
defaultSpecsArray.unshift(spec());
}
if (!names.has('doc')) {
defaultSpecsArray.unshift(spec$2());
}
flattenedSpecs = [...flatten$1(defaultSpecsArray), ...flattenedSpecs];
}
this._spec = flattenedSpecs;
this._schema = createSchema(this._spec);
this._options = Object.fromEntries(this._spec
.filter((spec) => spec.options)
.map((spec) => [spec.name, spec.options]));
}
get spec() {
return this._spec;
}
get schema() {
return this._schema;
}
get options() {
return this._options;
}
}
function createSchema(specRegistry) {
let nodes = [];
let marks = [];
let topNode;
for (const spec of specRegistry) {
if (spec.type === 'node') {
nodes.push([spec.name, spec.schema]);
}
else if (spec.type === 'mark') {
marks.push([spec.name, spec.schema]);
}
else if (spec.type === 'component') ;
else {
throw new Error(spec.name + ' unknown type: ' + spec.type);
}
if (spec.topNode === true) {
topNode = spec.name;
}
}
return new Schema({
topNode,
nodes: Object.fromEntries(nodes),
marks: Object.fromEntries(marks),
});
}
function validateSpec(spec) {
if (!spec.name) {
bangleWarn("The spec didn't have a name field", spec);
throw new Error('Invalid spec. Spec must have a name');
}
if (!['node', 'mark', 'component'].includes(spec.type)) {
bangleWarn('The spec must be of type node, mark or component ', spec);
throw new Error('Invalid spec type');
}
if (['node', 'mark'].includes(spec.type) && !spec.schema) {
bangleWarn("The spec of type 'mark' or 'node' must have a schema field", spec);
throw new Error('Invalid spec schema');
}
}
function flatten$1(data) {
const recurse = (d) => {
if (Array.isArray(d)) {
return d.flatMap((i) => recurse(i)).filter(Boolean);
}
// @ts-ignore really hard to annotate recursive functions
return d;
};
return recurse(data);
}
function pluginLoader(specRegistry, plugins$2, { metadata, editorProps, defaultPlugins = true, dropCursorOpts, transformPlugins = (p) => p, } = {}) {
const schema = specRegistry.schema;
const pluginPayload = {
schema,
specRegistry,
metadata: metadata,
};
let [flatPlugins, pluginGroupNames] = flatten(plugins$2, pluginPayload);
if (defaultPlugins) {
let defaultPluginGroups = [];
if (!pluginGroupNames.has('history')) {
defaultPluginGroups.push(plugins$1());
}
if (!pluginGroupNames.has('editorStateCounter')) {
defaultPluginGroups.push(plugins());
}
flatPlugins = flatPlugins.concat(
// TODO: deprecate the ability pass a callback to the plugins param of pluginGroup
flatten(defaultPluginGroups, pluginPayload)[0]);
flatPlugins = processInputRules(flatPlugins);
flatPlugins.push(keymap(baseKeymap), dropCursor(dropCursorOpts), gapCursor());
}
if (editorProps) {
flatPlugins.push(new Plugin({
props: editorProps,
}));
}
flatPlugins = flatPlugins.filter(Boolean);
flatPlugins = transformPlugins(flatPlugins);
if (flatPlugins.some((p) => !(p instanceof Plugin))) {
bangleWarn('You are either using multiple versions of the library or not returning a Plugin class in your plugins. Investigate :', flatPlugins.find((p) => !(p instanceof Plugin)));
throw new Error('Invalid plugin');
}
validateNodeViews(flatPlugins, specRegistry);
return flatPlugins;
}
function processInputRules(plugins, { inputRules: inputRules$1 = true, undoInputRule: undoInputRule$1 = true } = {}) {
let newPlugins = [];
let match = [];
plugins.forEach((plugin) => {
if (plugin instanceof InputRule) {
match.push(plugin);
return;
}
newPlugins.push(plugin);
});
if (inputRules$1) {
plugins = [
...newPlugins,
inputRules({
rules: match,
}),
];
}
if (undoInputRule$1) {
plugins.push(keymap({
Backspace: undoInputRule,
}));
}
return plugins;
}
function validateNodeViews(plugins, specRegistry) {
const nodeViewPlugins = plugins.filter((p) => p.props && p.props.nodeViews);
const nodeViewNames = new Map();
for (const plugin of nodeViewPlugins) {
for (const name of Object.keys(plugin.props.nodeViews)) {
if (!specRegistry.schema.nodes[name]) {
bangleWarn(`When loading your plugins, we found nodeView implementation for the node '${name}' did not have a corresponding spec. Check the plugin:`, plugin, 'and your specRegistry', specRegistry);
throw new Error(`NodeView validation failed. Spec for '${name}' not found.`);
}
if (nodeViewNames.has(name)) {
bangleWarn(`When loading your plugins, we found more than one nodeView implementation for the node '${name}'. Bangle can only have a single nodeView implementation, please check the following two plugins`, plugin, nodeViewNames.get(name));
throw new Error(`NodeView validation failed. Duplicate nodeViews for '${name}' found.`);
}
nodeViewNames.set(name, plugin);
}
}
}
function flatten(rawPlugins, callbackPayload) {
const pluginGroupNames = new Set();
const recurse = (plugins) => {
if (Array.isArray(plugins)) {
return plugins.flatMap((p) => recurse(p)).filter(Boolean);
}
if (plugins instanceof PluginGroup) {
if (pluginGroupNames.has(plugins.name)) {
throw new Error(`Duplicate names of pluginGroups ${plugins.name} not allowed.`);
}
pluginGroupNames.add(plugins.name);
return recurse(plugins.plugins);
}
if (typeof plugins === 'function') {
if (!callbackPayload) {
throw new Error('Found a function but no payload');
}
return recurse(plugins(callbackPayload));
}
return plugins;
};
return [recurse(rawPlugins), pluginGroupNames];
}
class BangleEditorState {
constructor({ specRegistry, specs, plugins = () => [], initialValue, editorProps, pmStateOpts, pluginMetadata, dropCursorOpts, } = {}) {
if (specs && specRegistry) {
throw new Error('Cannot have both specs and specRegistry defined');
}
if (!specRegistry) {
specRegistry = new SpecRegistry(specs);
}
if (Array.isArray(plugins)) {
console.warn('The use plugins as an array is deprecated, please pass a function which returns an array of plugins. Refer: https://bangle.dev/docs/api/core#bangleeditorstate');
}
this.specRegistry = specRegistry;
const schema = this.specRegistry.schema;
const pmPlugins = pluginLoader(specRegistry, plugins, {
editorProps,
metadata: pluginMetadata,
dropCursorOpts,
});
this.pmState = EditorState.create(Object.assign({ schema, doc: createDocument({ schema, content: initialValue }), plugins: pmPlugins }, pmStateOpts));
}
}
const createDocument = ({ schema, content, parseOptions, }) => {
const emptyDocument = {
type: 'doc',
content: [
{
type: 'paragraph',
},
],
};
if (content == null) {
return schema.nodeFromJSON(emptyDocument);
}
if (content instanceof Node) {
return content;
}
if (typeof content === 'object') {
return schema.nodeFromJSON(content);
}
if (typeof content === 'string') {
const element = document.createElement('div');
element.innerHTML = content.trim();
return DOMParser.fromSchema(schema).parse(element, parseOptions);
}
return undefined;
};
class BangleEditor {
constructor(element, { focusOnInit = true, state, pmViewOpts = {}, }) {
this.destroyed = false;
if (!(state instanceof BangleEditorState)) {
throw new Error('state is required and must be an instance of BangleEditorState');
}
this.view = new EditorView(element, Object.assign({ state: state.pmState, dispatchTransaction: (transaction) => {
const newState = this.view.state.apply(transaction);
this.view.updateState(newState);
}, attributes: { class: 'bangle-editor' } }, pmViewOpts));
if (focusOnInit) {
this.focusView();
}
}
focusView() {
if (isTestEnv || this.view.hasFocus()) {
return;
}
this.view.focus();
}
destroy() {
if (this.destroyed) {
return;
}
// EditorView.docView and isDestroyed is missing in @types/prosemirror-view
if (
// if using earlier version of pm `isDestroyed` is undefined
typeof this.view.isDestroyed === 'boolean'
? this.view.isDestroyed
: this.view.docView === null) {
this.destroyed = true;
return;
}
this.destroyed = true;
this.view.destroy();
}
toHTMLString() {
return toHTMLString(this.view.state);
}
}
function createElement(spec) {
const { dom, contentDOM } = DOMSerializer.renderSpec(window.document, spec);
if (contentDOM) {
throw new Error('createElement does not support creating contentDOM');
}
return dom;
}
const renderHandlersCache = new WeakMap();
class BaseNodeView {
constructor({ node, view, getPos, decorations, containerDOM, contentDOM,
// Defaults to whatever is set by the rendering framework which ideally
// would have called the method `saveRenderHandlers` before this gets
// executed.
renderHandlers = getRenderHandlers(view), }, { selectionSensitive = true } = {}) {
// by PM
this._node = node;
this._view = view;
this._getPos = getPos;
this._decorations = decorations;
this._selected = false;
if (!renderHandlers) {
bangleWarn('It appears the view =', view, ' was not associated with renderHandlers. You are either using nodeViews accidentally or have incorrectly setup nodeViews');
throw new Error('You either did not pass the renderHandlers correct or it cannot find render handlers associated with the view.');
}
this.renderHandlers = renderHandlers;
// by the implementor
this.containerDOM = containerDOM;
this.contentDOM = contentDOM;
if (this.contentDOM) {
// This css rule makes sure the content dom has non-zero width
// so that folks can type inside it
this.contentDOM.classList.add('bangle-nv-content');
}
if (this.containerDOM) {
this.containerDOM.classList.add('bangle-nv-container');
}
if (this._node.type.isAtom && this.contentDOM) {
throw new Error('An atom node cannot have a contentDOM');
}
this.opts = {
selectionSensitive,
};
this.renderHandlers.create(this, this.getNodeViewProps());
}
getAttrs() {
return this._node.attrs;
}
getNodeViewProps() {
return {
node: this._node,
view: this._view,
getPos: this._getPos,
decorations: this._decorations,
selected: this._selected,
attrs: this._node.attrs,
updateAttrs: (attrs) => {
this._view.dispatch(updateAttrs(this._getPos(), this._node, attrs, this._view.state.tr));
},
};
}
// for pm to get hold of containerDOM
// this exists as the name `dom` is too ambiguous
get dom() {
return this.containerDOM;
}
}
// TODO this is adds unneeded abstraction
// maybe we can lessen the amount of things it is doing
// and the abstraction.
class NodeView extends BaseNodeView {
/**
* The idea here is to figure out whether your component
* will be hole-y (will let pm put in contents) or be opaque (example emoji).
* NOTE: if passing contentDOM, it is your responsibility to insert it into
* containerDOM.
* NOTE: when dealing with renderHandlers like .create or .update
* donot assume anything about the current state of dom elements. For
* example, the dom you created in .create handler, may or may not exist,
* when the .update is called.
*
*/
static createPlugin({ name, containerDOM: containerDOMSpec, contentDOM: contentDOMSpec, // only for components which need to have editable content
renderHandlers, }) {
return new Plugin({
key: new PluginKey(name + 'NodeView'),
props: {
nodeViews: {
[name]: (node, view, getPos, decorations) => {
const containerDOM = createElement(containerDOMSpec);
let contentDOM;
if (contentDOMSpec) {
contentDOM = createElement(contentDOMSpec);
}
// getPos for custom marks is boolean
getPos = getPos;
return new NodeView({
node,
view,
getPos,
decorations,
containerDOM,
contentDOM,
renderHandlers,
});
},
},
},
});
}
update(node, decorations) {
// https://github.com/ProseMirror/prosemirror/issues/648
if (this._node.type !== node.type) {
return false;
}
if (this._node === node && this._decorations === decorations) {
return true;
}
this._node = node;
this._decorations = decorations;
this.renderHandlers.update(this, this.getNodeViewProps());
return true;
}
selectNode() {
this.containerDOM.classList.add('ProseMirror-selectednode');
this._selected = true;
this.renderHandlers.update(this, this.getNodeViewProps());
}
deselectNode() {
this.containerDOM.classList.remove('ProseMirror-selectednode');
this._selected = false;
this.renderHandlers.update(this, this.getNodeViewProps());
}
// Donot unset it if you donot have an implementation.
// Unsetting this is dangerous as it fucks up elements who have editable content inside them.
// setSelection(...args) {
// console.log('hi', ...args);
// }
// PM essentially works by watching mutation and then syncing the two states: its own and the DOM.
ignoreMutation(mutation) {
// For PM an atom node is a black box, what happens inside it are of no concern to PM
// and should be ignored.
if (this._node.type.isAtom) {
return true;
}
// donot ignore a selection type mutation
if (mutation.type === 'selection') {
return false;
}
// if a child of containerDOM (the one handled by PM)
// has any mutation, do not ignore it
if (this.containerDOM.contains(mutation.target)) {
return false;
}
// if the contentDOM itself was the target
// do not ignore it. This is important for schema where
// content: 'inline*' and you end up delete all the content with backspace
// PM needs to step in and create an empty node.
if (mutation.target === this.contentDOM) {
return false;
}
return true;
}
// stopEvent() {
// return true;
// }
destroy() {
this.renderHandlers.destroy(this, this.getNodeViewProps());
this.containerDOM = undefined;
this.contentDOM = undefined;
}
}
function saveRenderHandlers(editorContainer, handlers) {
if (renderHandlersCache.has(editorContainer)) {
throw new Error('It looks like renderHandlers were already set by someone else.');
}
renderHandlersCache.set(editorContainer, handlers);
}
function getRenderHandlers(view) {
// TODO this assumes parentNode is one level above root
// lets make sure it always is or rewrite this to
// traverse the ancestry.
let editorContainer = view.dom.parentNode;
const handlers = renderHandlersCache.get(editorContainer);
return handlers;
}
function updateAttrs(pos, node, newAttrs, tr) {
return tr.setNodeMarkup(pos, undefined, Object.assign(Object.assign({}, node.attrs), newAttrs));
}
/**
* Creates a bare bone `toDOM` and `parseDOM` handlers for the PM schema.
* The use case is for nodes or marks who already have a nodeView
* and want to get basic `toDOM`, `parseDOM` to enable drag n drop or
* copy paste.
*
* @param {*} spec
* @param {Object} opts
* @param {string} opts.tag
* @param {string|0|(node)=>string} opts.content - `0` signals content that PM will inject.
* @param {string} opts.ignoreAttrs
* @param {Number} opts.parsingPriority https://prosemirror.net/docs/ref/#model.ParseRule.priority
*/
function domSerializationHelpers(name, { tag = 'div', content, ignoreAttrs = [], parsingPriority = 51, } = {}) {
const serializer = (node) => JSON.stringify(objectFilter(node.attrs || {}, (_value, key) => !ignoreAttrs.includes(key)));
return {
toDOM: (node) => {
const domSpec = [
tag,
{
'data-bangle-name': name,
'data-bangle-attrs': serializer(node),
},
];
if (content !== undefined) {
if (typeof content === 'function') {
domSpec.push(content(node));
}
else {
domSpec.push(content);
}
}
return domSpec;
},
parseDOM: [
{
priority: parsingPriority,
tag: `${tag}[data-bangle-name="${name}"]`,
getAttrs: (dom) => {
const attrs = dom.getAttribute('data-bangle-attrs');
if (!attrs) {
return {};
}
return JSON.parse(attrs);
},
},
],
};
}
// components
import * as doc from './critical-components/doc';
import * as paragraph from './critical-components/paragraph';
import * as text from './critical-components/text';
import * as history from './critical-components/history';
import * as editorStateCounter from './critical-components/editor-state-counter';
export * from './bangle-editor';
export * from './bangle-editor-state';
export * from './create-element';
export * from './node-view';
export * from './plugin';
export * from './spec-registry';
export * from './dom-serialization-helpers';
export const criticalComponents = {
const criticalComponents = {
doc,

@@ -21,2 +797,3 @@ paragraph,

};
//# sourceMappingURL=index.js.map
export { BangleEditor, BangleEditorState, NodeView, SpecRegistry, createElement, criticalComponents, doc, domSerializationHelpers, editorStateCounter, getRenderHandlers, history, paragraph, saveRenderHandlers, text };
{
"name": "@bangle.dev/core",
"version": "0.28.11",
"version": "0.29.0-alpha.0",
"homepage": "https://bangle.dev",

@@ -12,2 +12,12 @@ "authors": [

],
"type": "module",
"main": "dist/index.cjs",
"module": "dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./style.css": "./style.css"
},
"license": "MIT",

@@ -21,5 +31,2 @@ "repository": {

},
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"style": "style.css",

@@ -29,4 +36,3 @@ "scripts": {

"compile-ts": "yarn g:tsc --build $INIT_CWD",
"watch-ts": "yarn g:tsc -p $INIT_CWD -w",
"prepack": "yarn compile-ts"
"watch-ts": "yarn g:tsc -p $INIT_CWD -w"
},

@@ -37,21 +43,20 @@ "peerDependencies": {

"dependencies": {
"@bangle.dev/pm-commands": "0.28.11",
"@bangle.dev/utils": "0.28.11"
"@bangle.dev/pm-commands": "0.29.0-alpha.0",
"@bangle.dev/utils": "0.29.0-alpha.0",
"@types/jest": "^27.0.4"
},
"devDependencies": {
"@bangle.dev/pm": "0.28.11",
"@types/jest": "^26.0.23",
"@bangle.dev/pm": "0.29.0-alpha.0",
"@types/markdown-it": "^12.0.3",
"@types/prosemirror-markdown": "^1.5.2",
"markdown-it": "^10.0.0",
"prettier": "^2.3.2",
"prosemirror-markdown": "^1.5.1"
"prettier": "^2.6.2",
"prosemirror-markdown": "^1.9.1"
},
"publishConfig": {
"access": "public",
"main": "dist/index.js",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts"
},
"type": "module"
"types": "dist/index.d.ts"
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc