@zodiac-ui/editor
Version:
A rich text editor for Angular based on `@atlaskit/editor-core`.
1,348 lines (1,310 loc) • 193 kB
JavaScript
import { ReplaySubject, Subject } from 'rxjs';
import { findWrapping } from 'prosemirror-transform';
import { history, redo, undo } from 'prosemirror-history';
import * as BaseCodeMirrorNs from 'codemirror';
import 'codemirror/addon/mode/overlay';
import 'codemirror/addon/mode/multiplex';
import 'codemirror/addon/mode/simple';
import 'codemirror/mode/meta';
import { Schema, Node, Fragment, Slice } from 'prosemirror-model';
import * as LinkifyItNs from 'linkify-it';
import { EditorView, DecorationSet, Decoration } from 'prosemirror-view';
import { keydownHandler, keymap } from 'prosemirror-keymap';
import { EditorState, Selection, Plugin, PluginKey, TextSelection, NodeSelection } from 'prosemirror-state';
import { InputRule, textblockTypeInputRule, wrappingInputRule, inputRules, undoInputRule } from 'prosemirror-inputrules';
import { baseKeymap, chainCommands, exitCode, toggleMark } from 'prosemirror-commands';
import { __assign, __spread, __awaiter, __generator, __read, __extends } from 'tslib';
import { CommonModule } from '@angular/common';
import { InjectionToken, EventEmitter, Inject, Injectable, Optional, ChangeDetectionStrategy, Component, ElementRef, Input, Output, ViewEncapsulation, NgModule, Directive, ComponentFactoryResolver, ChangeDetectorRef, HostBinding, HostListener, Injector } from '@angular/core';
import { ComponentPortal } from '@angular/cdk/portal';
import { findParentNodeOfType, hasParentNodeOfType, safeInsert, findParentDomRefOfType, removeParentNodeOfType, setParentNodeMarkup } from 'prosemirror-utils';
import { ConnectionPositionPair, OverlayRef, Overlay } from '@angular/cdk/overlay';
import { MatButtonModule, MatIconModule, MatSelectModule, MatMenuModule, MatTooltipModule, MatFormFieldModule, MatInputModule } from '@angular/material';
var findChangedNodesFromTransaction = function (tr) {
var nodes = [];
var steps = (tr.steps || []);
steps.forEach(function (step) {
var to = step.to, from = step.from, slice = step.slice;
var size = slice && slice.content ? slice.content.size : 0;
var _loop_1 = function (i) {
if (i <= tr.doc.content.size) {
var topLevelNode_1 = tr.doc.resolve(i).node(1);
if (topLevelNode_1 && !nodes.find(function (n) { return n === topLevelNode_1; })) {
nodes.push(topLevelNode_1);
}
}
};
for (var i = from; i <= to + size; i++) {
_loop_1(i);
}
});
return nodes;
};
/** Validates prosemirror nodes, and returns true only if all nodes are valid */
var validateNodes = function (nodes) {
return nodes.every(function (node) {
try {
node.check(); // this will throw an error if the node is invalid
}
catch (error) {
return false;
}
return true;
});
};
var EventDispatcher = /** @class */ (function () {
function EventDispatcher() {
this.listeners = {};
}
EventDispatcher.prototype.on = function (event, cb) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(cb);
};
EventDispatcher.prototype.off = function (event, cb) {
if (!this.listeners[event]) {
return;
}
this.listeners[event] = this.listeners[event].filter(function (callback) { return callback !== cb; });
};
EventDispatcher.prototype.emit = function (event, data) {
if (!this.listeners[event]) {
return;
}
this.listeners[event].forEach(function (cb) { return cb(data); });
};
EventDispatcher.prototype.destroy = function () {
this.listeners = {};
};
return EventDispatcher;
}());
function createPMPlugins(_a) {
var editorConfig = _a.editorConfig, schema = _a.schema, dispatch = _a.dispatch, eventDispatcher = _a.eventDispatcher;
return editorConfig.pmPlugins
// .sort(sortByOrder("plugins"))
.map(function (_a) {
var plugin = _a.plugin;
return plugin({
schema: schema,
dispatch: dispatch,
// providerFactory,
// errorReporter,
eventDispatcher: eventDispatcher,
});
})
.filter(function (plugin) { return !!plugin; });
}
var docNode = {
content: '(block|layoutSection)+',
marks: 'alignment breakout indentation link',
};
// import { Inline, MarksObject, NoMark } from './doc';
// import { AlignmentMarkDefinition, IndentationMarkDefinition } from '../marks';
// /**
// * @name paragraph_node
// */
// export interface ParagraphBaseDefinition {
// type: 'paragraph';
// /**
// * @allowUnsupportedInline true
// */
// content?: Array<Inline>;
// marks?: Array<any>;
// }
//
// /**
// * @name paragraph_with_no_marks_node
// */
// export type ParagraphDefinition = ParagraphBaseDefinition & NoMark;
//
// /**
// * NOTE: Need this because TS is too smart and inline everything.
// * So we need to give them separate identity.
// * Probably there's a way to solve it but that will need time and exploration.
// * // http://bit.ly/2raXFX5
// * type T1 = X | Y
// * type T2 = A | T1 | B // T2 = A | X | Y | B
// */
//
// /**
// * @name paragraph_with_alignment_node
// * @stage 0
// */
// export type ParagraphWithAlignmentDefinition = ParagraphBaseDefinition &
// MarksObject<AlignmentMarkDefinition>;
//
// /**
// * @name paragraph_with_indentation_node
// * @stage 0
// */
// export type ParagraphWithIndentationDefinition = ParagraphBaseDefinition &
// MarksObject<IndentationMarkDefinition>;
//
// export type ParagraphWithMarksDefinition =
// | ParagraphWithAlignmentDefinition
// | ParagraphWithIndentationDefinition;
var pDOM = ['p', 0];
var paragraphNode = {
content: 'inline*',
group: 'block',
marks: 'strong code em link strike subsup textColor typeAheadQuery underline mentionQuery emojiQuery confluenceInlineComment action annotation',
parseDOM: [{ tag: 'p' }],
toDOM: function () {
return pDOM;
},
};
var ALIGNMENT = "alignment";
var INDENTATION = "indentation";
var alignment = {
excludes: ALIGNMENT + " " + INDENTATION,
group: ALIGNMENT,
attrs: {
align: {},
},
parseDOM: [
{
tag: 'div.fabric-editor-block-mark',
getAttrs: function (dom) {
var align = dom.getAttribute('data-align');
return align ? { align: align } : false;
},
},
],
toDOM: function (mark) {
return [
'div',
{
class: "fabric-editor-block-mark fabric-editor-align-" + mark.attrs.align,
'data-align': mark.attrs.align,
},
0,
];
},
};
function isContentSupported(nodes, contentKey) {
var nodeKeys = Object.keys(nodes);
// content is with valid node
if (nodeKeys.indexOf(contentKey) > -1) {
return true;
}
// content is with valid group
// tslint:disable-next-line
for (var supportedKey in nodes) {
var nodeSpec = nodes[supportedKey];
if (nodeSpec && nodeSpec.group === contentKey) {
return true;
}
}
return false;
}
function sanitizedContent(content, invalidContent) {
if (!invalidContent.length) {
return content || '';
}
if (!content || !content.match(/\w/)) {
return '';
}
var newContent = content
.replace(new RegExp("(" + invalidContent + "((\\s)*\\|)+)|((\\|(\\s)*)+" + invalidContent + ")|(" + invalidContent + "$)", 'g'), '')
.replace(' ', ' ')
.trim();
return newContent;
}
function sanitizeNodes(nodes, supportedMarks) {
var nodeNames = Object.keys(nodes);
nodeNames.forEach(function (nodeKey) {
var nodeSpec = __assign({}, nodes[nodeKey]);
if (nodeSpec.marks && nodeSpec.marks !== '_') {
nodeSpec.marks = nodeSpec.marks
.split(' ')
.filter(function (mark) { return !!supportedMarks[mark]; })
.join(' ');
}
if (nodeSpec.content) {
var content = nodeSpec.content.replace(/\W/g, ' ');
var contentKeys = content.split(' ');
var unsupportedContentKeys = contentKeys.filter(function (contentKey) { return !isContentSupported(nodes, contentKey); });
nodeSpec.content = unsupportedContentKeys.reduce(function (newContent, nodeName) { return sanitizedContent(newContent, nodeName); }, nodeSpec.content);
}
nodes[nodeKey] = nodeSpec;
});
return nodes;
}
var textNode = {
group: 'inline',
};
var markGroupDeclarations = [
// groupDeclaration(COLOR),
// groupDeclaration(FONT_STYLE),
// groupDeclaration(SEARCH_QUERY),
// groupDeclaration(LINK),
];
var ɵ0 = function (groupMark) { return groupMark.name; };
var markGroupDeclarationsNames = markGroupDeclarations.map(ɵ0);
function sortByOrder(item) {
// return function(a: { name: string }, b: { name: string }): number {
// return Ranks[item].indexOf(a.name) - Ranks[item].indexOf(b.name);
// };
return function () { return 0; };
}
function fixExcludes(marks) {
var markKeys = Object.keys(marks);
var markGroups = new Set(markKeys.map(function (mark) { return marks[mark].group; }));
markKeys.map(function (markKey) {
var mark = marks[markKey];
if (mark.excludes) {
mark.excludes = mark.excludes
.split(' ')
.filter(function (group) { return markGroups.has(group); })
.join(' ');
}
});
return marks;
}
function createSchema(editorConfig) {
var marks = fixExcludes(editorConfig.marks.sort(sortByOrder('marks')).reduce(function (acc, mark) {
acc[mark.name] = mark.mark;
return acc;
}, {}));
var nodes = sanitizeNodes(editorConfig.nodes.sort(sortByOrder('nodes')).reduce(function (acc, node) {
acc[node.name] = node.node;
return acc;
}, {}), marks);
return new Schema({ nodes: nodes, marks: marks });
}
/**
* Creates a dispatch function that can be called inside ProseMirror Plugin
* to notify listeners about that plugin's state change.
*/
function createDispatch(eventDispatcher) {
return function (eventName, data) {
if (!eventName) {
throw new Error('event name is required!');
}
var event = typeof eventName === 'string'
? eventName
: eventName.key;
eventDispatcher.emit(event, data);
};
}
function createConfig(plugins, editorProps) {
var config = {
nodes: [],
marks: [],
pmPlugins: [],
};
var pluginsOptions = plugins.reduce(function (acc, plugin) {
if (plugin.pluginsOptions) {
Object.keys(plugin.pluginsOptions).forEach(function (pluginName) {
if (!acc[pluginName]) {
acc[pluginName] = [];
}
acc[pluginName].push(plugin.pluginsOptions[pluginName]);
});
}
return acc;
}, {});
return plugins.reduce(function (acc, plugin) {
var _a, _b, _c;
if (plugin.pmPlugins) {
(_a = acc.pmPlugins).push.apply(_a, __spread(plugin.pmPlugins(plugin.name ? pluginsOptions[plugin.name] : undefined)));
}
if (plugin.nodes) {
(_b = acc.nodes).push.apply(_b, __spread(plugin.nodes(editorProps)));
}
if (plugin.marks) {
(_c = acc.marks).push.apply(_c, __spread(plugin.marks(editorProps)));
}
//
// if (plugin.contentComponent) {
// acc.contentComponents.push(plugin.contentComponent);
// }
//
// if (plugin.primaryToolbarComponent) {
// acc.primaryToolbarComponents.push(plugin.primaryToolbarComponent);
// }
//
// if (plugin.secondaryToolbarComponent) {
// acc.secondaryToolbarComponents.push(plugin.secondaryToolbarComponent);
// }
return acc;
}, config);
}
var EDITOR_PLUGIN = new InjectionToken("EDITOR_PLUGIN");
var STATE_HANDLER = new InjectionToken("STATE_HANDLER");
var defaultState = {
content: [],
type: "doc",
};
var EditorService = /** @class */ (function () {
function EditorService(plugins, handlers) {
this.eventDispatcher = new EventDispatcher();
this.viewChange = new EventEmitter();
this.stateChange = new ReplaySubject(1);
this.plugins = plugins;
this.handlers = handlers;
}
EditorService.prototype.runTool = function (tool) {
tool.run(this.state, this.view.dispatch, this.view);
};
EditorService.prototype.updateState = function (state) {
if (this.view) {
this.createEditorState(state);
this.view.updateState(this.state);
}
};
EditorService.prototype.createEditorState = function (state) {
this.config = createConfig(this.plugins, {});
var schema = createSchema(this.config);
var dispatch = createDispatch(this.eventDispatcher);
var doc = Node.fromJSON(schema, state ? state.doc : defaultState);
var selection = state ? Selection.fromJSON(doc, state.selection) : undefined;
// const errorReporter = createErrorReporter(errorReporterHandler);
var plugins = createPMPlugins({
schema: schema,
dispatch: dispatch,
editorConfig: this.config,
eventDispatcher: this.eventDispatcher,
});
// let doc;
// if (options.replaceDoc) {
// doc =
// this.contentTransformer && typeof defaultValue === 'string'
// ? this.contentTransformer.parse(defaultValue)
// : processRawValue(schema, defaultValue);
// }
// let selection: Selection | undefined;
// if (doc) {
// // ED-4759: Don't set selection at end for full-page editor - should be at start
// selection =
// options.props.editorProps.appearance === 'full-page'
// ? Selection.atStart(doc)
// : Selection.atEnd(doc);
// }
// // Workaround for ED-3507: When media node is the last element, scrollIntoView throws an error
// const patchedSelection = selection
// ? Selection.findFrom(selection.$head, -1, true) || undefined
// : undefined;
this.state = EditorState.create({
schema: schema,
plugins: plugins,
doc: doc,
selection: selection
});
};
EditorService.prototype.createEditorView = function (node) {
var _this = this;
// Creates the editor-view from this.editorState. If an editor has been mounted
// previously, this will contain the previous state of the editor.
this.view = new EditorView({ mount: node }, {
state: this.state,
dispatchTransaction: function (transaction) {
if (!_this.view) {
return;
}
var nodes = findChangedNodesFromTransaction(transaction);
if (validateNodes(nodes)) {
// go ahead and update the state now we know the transaction is good
var editorState = _this.view.state.apply(transaction);
_this.view.updateState(editorState);
if (transaction.docChanged) {
_this.viewChange.emit(_this);
}
_this.state = editorState;
_this.stateChange.next(_this);
}
// else {
// const documents = {
// new: getDocStructure(transaction.doc),
// prev: getDocStructure(transaction.docs[0]),
// };
// }
},
});
this.viewChange.emit(this);
this.stateChange.next(this);
if (this.handlers) {
this.stateChange.subscribe(function (editor) {
_this.handlers.forEach(function (handler) { return handler(editor); });
});
}
};
EditorService.decorators = [
{ type: Injectable }
];
/** @nocollapse */
EditorService.ctorParameters = function () { return [
{ type: Array, decorators: [{ type: Inject, args: [EDITOR_PLUGIN,] }] },
{ type: Array, decorators: [{ type: Optional }, { type: Inject, args: [STATE_HANDLER,] }] }
]; };
return EditorService;
}());
function hasChanges(change) {
return change && change.previousValue !== change.currentValue;
}
var EditorComponent = /** @class */ (function () {
function EditorComponent(editor, editorRef) {
this.editor = editor;
this.viewChange = editor.viewChange;
this.stateChange = editor.stateChange;
this.editorRef = editorRef;
}
EditorComponent.prototype.ngOnInit = function () {
this.editor.createEditorState(this.state);
this.editor.createEditorView(this.editorRef.nativeElement);
};
EditorComponent.prototype.ngOnChanges = function (changes) {
if (hasChanges(changes.state)) {
this.editor.updateState(this.state);
}
};
EditorComponent.prototype.runTool = function (command) {
this.editor.runTool(command);
};
EditorComponent.decorators = [
{ type: Component, args: [{
selector: "z-editor",
template: "<!-- use host -->",
viewProviders: [EditorService],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
styles: [".ProseMirror{display:block;overflow-wrap:break-word;white-space:pre-wrap}.ProseMirror:focus{outline:0}.fabric-editor-align-center{text-align:center}.fabric-editor-align-end{text-align:right}.CodeMirror{border:1px solid #eee;height:auto}"]
}] }
];
/** @nocollapse */
EditorComponent.ctorParameters = function () { return [
{ type: EditorService },
{ type: ElementRef }
]; };
EditorComponent.propDecorators = {
state: [{ type: Input }],
viewChange: [{ type: Output }],
stateChange: [{ type: Output }]
};
return EditorComponent;
}());
var EditorModule = /** @class */ (function () {
function EditorModule() {
}
EditorModule.decorators = [
{ type: NgModule, args: [{
imports: [
CommonModule,
],
declarations: [EditorComponent],
exports: [EditorComponent],
},] }
];
return EditorModule;
}());
var EditorToolbar = /** @class */ (function () {
function EditorToolbar() {
}
return EditorToolbar;
}());
var EditorToolbarComponent = /** @class */ (function () {
function EditorToolbarComponent() {
this.viewChange = new Subject();
this.stateChange = new Subject();
}
EditorToolbarComponent.prototype.ngOnInit = function () {
this.editor.viewChange.subscribe(this.viewChange);
this.editor.stateChange.subscribe(this.stateChange);
};
EditorToolbarComponent.prototype.runTool = function (tool) {
if (this.editor) {
this.editor.runTool(tool);
}
};
EditorToolbarComponent.decorators = [
{ type: Component, args: [{
selector: "z-editor-toolbar",
template: "\n <ng-content></ng-content>\n ",
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [{
provide: EditorToolbar,
useExisting: EditorToolbarComponent
}],
styles: [""]
}] }
];
/** @nocollapse */
EditorToolbarComponent.ctorParameters = function () { return []; };
EditorToolbarComponent.propDecorators = {
editor: [{ type: Input }]
};
return EditorToolbarComponent;
}());
var EditorToolbarButtonDirective = /** @class */ (function () {
function EditorToolbarButtonDirective() {
}
EditorToolbarButtonDirective.decorators = [
{ type: Directive, args: [{
selector: "[zEditorToolbarButton]",
},] }
];
/** @nocollapse */
EditorToolbarButtonDirective.ctorParameters = function () { return []; };
EditorToolbarButtonDirective.propDecorators = {
zEditorToolbarButton: [{ type: Input }]
};
return EditorToolbarButtonDirective;
}());
var EditorToolbarModule = /** @class */ (function () {
function EditorToolbarModule() {
}
EditorToolbarModule.decorators = [
{ type: NgModule, args: [{
declarations: [EditorToolbarComponent, EditorToolbarButtonDirective],
exports: [EditorToolbarComponent],
imports: [
CommonModule,
MatButtonModule,
MatSelectModule,
MatIconModule
],
},] }
];
return EditorToolbarModule;
}());
var getActiveAlignment = function (state) {
var node = findParentNodeOfType([
state.schema.nodes.paragraph,
state.schema.nodes.heading,
])(state.selection);
var getMark = node &&
node.node.marks.filter(function (mark) { return mark.type === state.schema.marks.alignment; })[0];
return (getMark && getMark.attrs.align) || 'start';
};
var toggleBlockMark = function (markType, getAttrs, allowedBlocks) { return function (state, dispatch) {
var _a = state.selection, from = _a.from, to = _a.to;
var markApplied = false;
var tr = state.tr;
state.doc.nodesBetween(from, to, function (node, pos, parent) {
if (!node.type.isBlock) {
return false;
}
if ((!allowedBlocks ||
(Array.isArray(allowedBlocks)
? allowedBlocks.indexOf(node.type) > -1
: allowedBlocks(state.schema, node, parent))) &&
parent.type.allowsMarkType(markType)) {
var oldMarks = node.marks.filter(function (mark) { return mark.type === markType; });
var prevAttrs = oldMarks.length ? oldMarks[0].attrs : undefined;
var newAttrs = getAttrs(prevAttrs, node);
if (newAttrs !== undefined) {
tr.setNodeMarkup(pos, node.type, node.attrs, node.marks
.filter(function (mark) { return !markType.excludes(mark.type); })
.concat(newAttrs === false ? [] : markType.create(newAttrs)));
markApplied = true;
}
}
});
if (markApplied && tr.docChanged) {
if (dispatch) {
dispatch(tr.scrollIntoView());
}
return true;
}
return false;
}; };
var cascadeCommands = function (cmds) { return function (state, dispatch) {
var baseTr = state.tr;
var shouldDispatch = false;
var onDispatchAction = function (tr) {
tr.steps.forEach(function (st) {
baseTr.step(st);
});
shouldDispatch = true;
};
cmds.forEach(function (cmd) {
cmd(state, onDispatchAction);
});
if (dispatch && shouldDispatch) {
dispatch(baseTr);
return true;
}
return false;
}; };
var isAlignable = function (align) { return function (state, dispatch) {
var _a = state.schema, _b = _a.nodes, paragraph = _b.paragraph, heading = _b.heading, alignment = _a.marks.alignment;
return toggleBlockMark(alignment, function () { return (!align ? undefined : align === "start" ? false : { align: align }); }, [paragraph, heading])(state, dispatch);
}; };
var changeAlignment = function (align) { return function (state, dispatch) {
var _a = state.schema, _b = _a.nodes, paragraph = _b.paragraph, heading = _b.heading, alignment = _a.marks.alignment;
return cascadeCommands([
changeImageAlignment(align),
toggleBlockMark(alignment, function () {
return (!align ? undefined : align === 'start' ? false : { align: align });
}, [paragraph, heading]),
])(state, dispatch);
}; };
var changeImageAlignment = function (align) { return function (state, dispatch) {
var _a = state.selection, from = _a.from, to = _a.to;
var tr = state.tr;
state.doc.nodesBetween(from, to, function (node, pos, parent) {
if (node.type === state.schema.nodes.mediaSingle) {
tr.setNodeMarkup(pos, undefined, __assign({}, node.attrs, { layout: align === 'center' ? 'center' : "align-" + align }));
}
});
if (tr.docChanged && dispatch) {
dispatch(tr.scrollIntoView());
return true;
}
return false;
}; };
var pluginKey = new PluginKey("alignmentPlugin");
var defaultConfig = {
align: 'left',
};
function createInitialPluginState(editorState, pluginConfig) {
return {
align: getActiveAlignment(editorState) || pluginConfig.align,
isEnabled: true,
};
}
function createPlugin(dispatch, pluginConfig) {
return new Plugin({
key: pluginKey,
state: {
init: function (config, editorState) {
return createInitialPluginState(editorState, pluginConfig);
},
apply: function (tr, state, prevState, nextState) {
var nextPluginState = getActiveAlignment(nextState);
var isEnabled = isAlignable(nextPluginState)(nextState,
/**
* NOTE: Stan is already making dispatch optional in another PR.
* We can remove this once it's merged.
*/
undefined);
var newState = __assign({}, state, { align: nextPluginState, isEnabled: isEnabled });
if (nextPluginState !== state.align || isEnabled !== state.isEnabled) {
dispatch(pluginKey, newState);
}
return newState;
},
},
});
}
var alignmentPlugin = {
name: "alignmentPlugin",
marks: function () {
return [{ name: "alignment", mark: alignment }];
},
pmPlugins: function () {
return [
{
name: "alignmentPlugin",
plugin: function (_a) {
var dispatch = _a.dispatch;
return createPlugin(dispatch, defaultConfig);
},
}
];
}
};
var ɵ0$1 = alignmentPlugin;
var AlignmentModule = /** @class */ (function () {
function AlignmentModule() {
}
AlignmentModule.decorators = [
{ type: NgModule, args: [{
providers: [{
provide: EDITOR_PLUGIN,
useValue: ɵ0$1,
multi: true
}],
},] }
];
return AlignmentModule;
}());
var hasInvalidSteps = function (tr) {
return (tr.steps || []).some(function (step) { return step.from > step.to; });
};
var filterStepsPlugin = function () {
return new Plugin({
filterTransaction: function (tr) {
if (hasInvalidSteps(tr)) {
// tslint:disable-next-line:no-console
console.warn('The transaction was blocked because it contains invalid steps', tr.steps);
return false;
}
return true;
},
});
};
var focusStateKey = new PluginKey('focusStatePlugin');
var focusHandlerPlugin = function (dispatch) {
return new Plugin({
key: focusStateKey,
state: {
init: function () { return true; },
apply: function (tr, wasEditorFocused) {
var meta = tr.getMeta(focusStateKey);
if (typeof meta === 'boolean') {
if (meta !== wasEditorFocused) {
dispatch(focusStateKey, meta);
return meta;
}
}
return wasEditorFocused;
},
},
props: {
handleDOMEvents: {
click: function (view) {
var isEditorFocused = focusStateKey.getState(view.state);
if (!isEditorFocused) {
view.dispatch(view.state.tr.setMeta(focusStateKey, view.hasFocus()));
}
return false;
},
focus: function (view) {
var isEditorFocused = focusStateKey.getState(view.state);
if (!isEditorFocused) {
view.dispatch(view.state.tr.setMeta(focusStateKey, true));
}
return false;
},
blur: function (view) {
var isEditorFocused = focusStateKey.getState(view.state);
if (isEditorFocused) {
view.dispatch(view.state.tr.setMeta(focusStateKey, false));
}
return false;
},
},
},
});
};
var filterCommands = function (predicates, cmd) {
return function (state, dispatch, view) {
if (!Array.isArray(predicates)) {
predicates = [predicates];
}
if (predicates.some(function (pred) { return !pred(state, view); })) {
return false;
}
return cmd(state, dispatch, view) || false;
};
};
// import { typeAheadPluginKey } from '../../../plugins/type-ahead';
// import { emojiPluginKey } from '../../../plugins/emoji/pm-plugins/main';
var newlinePreserveMarksKey = new PluginKey('newlinePreserveMarksPlugin');
var isSelectionEndOfParagraph = function (state) {
return state.selection.$to.parent.type === state.schema.nodes.paragraph &&
state.selection.$to.pos === state.doc.resolve(state.selection.$to.pos).end();
};
var isSelectionAligned = function (state) {
return !!state.selection.$to.parent.marks.find(function (m) { return m.type === state.schema.marks.alignment; });
};
var isTypeaheadNotDisplaying = function (state) { return true; };
// !typeAheadPluginKey.getState(state).active &&
// !emojiPluginKey.getState(state).queryActive;
var splitBlockPreservingMarks = function (state, dispatch) {
dispatch(state.tr.split(state.tr.mapping.map(state.selection.$from.pos), 1));
return true;
};
var newlinePreserveMarksPlugin = function () {
return new Plugin({
key: newlinePreserveMarksKey,
props: {
handleKeyDown: keydownHandler({
Enter: filterCommands([
isSelectionEndOfParagraph,
isSelectionAligned,
isTypeaheadNotDisplaying,
], splitBlockPreservingMarks),
}),
},
});
};
var ZWSP = '\u200b';
var inlineCursorTargetStateKey = new PluginKey('inlineCursorTargetPlugin');
var SPECIAL_NODES = ['mention', 'emoji'];
var isSpecial = function (node) {
return node && SPECIAL_NODES.indexOf(node.type.name) !== -1;
};
var findSpecialNodeAfter = function ($pos, tr) {
if (isSpecial($pos.nodeAfter)) {
return $pos.pos + 1;
}
var parentOffset = $pos.parentOffset, parent = $pos.parent;
var docSize = tr.doc.nodeSize - 2;
if (parentOffset === parent.content.size && $pos.pos + 1 < docSize - 2) {
var nodeAfter = tr.doc.resolve($pos.pos + 1).nodeAfter;
if (nodeAfter && isSpecial(nodeAfter.firstChild)) {
return $pos.pos + 2;
}
}
};
var findSpecialNodeBefore = function ($pos, tr) {
if (isSpecial($pos.nodeBefore)) {
return $pos.pos - 1;
}
if ($pos.pos === 0) {
return;
}
var parentOffset = $pos.parentOffset;
if (parentOffset === 0) {
var nodeBefore = tr.doc.resolve($pos.pos - 1).nodeBefore;
if (nodeBefore && isSpecial(nodeBefore.firstChild)) {
return $pos.pos - 2;
}
}
};
var inlineCursorTargetPlugin = function () {
return new Plugin({
key: inlineCursorTargetStateKey,
state: {
init: function () { return ({ positions: [] }); },
apply: function (tr) {
var selection = tr.selection;
var $from = selection.$from;
var positions = [];
var posAfter = findSpecialNodeAfter($from, tr);
var posBefore = findSpecialNodeBefore($from, tr);
if (posAfter !== undefined) {
positions.push(posAfter);
}
if (posBefore !== undefined) {
positions.push(posBefore);
}
return { positions: positions };
},
},
props: {
decorations: function (state) {
var doc = state.doc;
var positions = inlineCursorTargetStateKey.getState(state).positions;
if (positions && positions.length) {
var decorations = positions.map(function (position) {
var node = document.createElement('span');
node.appendChild(document.createTextNode(ZWSP));
return Decoration.widget(position, node, {
raw: true,
side: -1,
});
});
return DecorationSet.create(doc, decorations);
}
return null;
},
},
});
};
var basePlugin = {
pmPlugins: function () {
return [
{
name: 'filterStepsPlugin',
plugin: function () { return filterStepsPlugin(); },
},
{
name: 'inlineCursorTargetPlugin',
plugin: function () { return inlineCursorTargetPlugin(); },
},
{
name: 'focusHandlerPlugin',
plugin: function (_a) {
var dispatch = _a.dispatch;
return focusHandlerPlugin(dispatch);
},
},
{
name: 'newlinePreserveMarksPlugin',
plugin: newlinePreserveMarksPlugin,
},
{ name: 'history', plugin: function () { return history(); } },
// should be last :(
{
name: 'codeBlockIndent',
plugin: function () {
return keymap(__assign({}, baseKeymap, { 'Mod-[': function () { return true; }, 'Mod-]': function () { return true; } }));
},
},
];
},
};
var ɵ0$4 = basePlugin;
var BaseModule = /** @class */ (function () {
function BaseModule() {
}
BaseModule.decorators = [
{ type: NgModule, args: [{
providers: [{
provide: EDITOR_PLUGIN,
useValue: ɵ0$4,
multi: true
}],
},] }
];
return BaseModule;
}());
// The names of the blocks don't map precisely to schema nodes, because
// of concepts like "paragraph" <-> "Normal text" and "Unknown".
//
// (there are also different blocks for different types of panel, when
// they're really all just a panel node)
//
// Rather than half-match half-not, this plugin introduces its own
// nomenclature for what 'block type' is active.
var messages = ({
normal: {
id: 'fabric.editor.normal',
defaultMessage: 'Normal text',
description: 'This is the default text style',
},
heading1: {
id: 'fabric.editor.heading1',
defaultMessage: 'Heading 1',
description: 'Used for the title of a section of your document, headings run from 1 (largest size) to 6 (smallest size)',
},
heading2: {
id: 'fabric.editor.heading2',
defaultMessage: 'Heading 2',
description: 'Used for the title of a section of your document, headings run from 1 (largest size) to 6 (smallest size)',
},
heading3: {
id: 'fabric.editor.heading3',
defaultMessage: 'Heading 3',
description: 'Used for the title of a section of your document, headings run from 1 (largest size) to 6 (smallest size)',
},
heading4: {
id: 'fabric.editor.heading4',
defaultMessage: 'Heading 4',
description: 'Used for the title of a section of your document, headings run from 1 (largest size) to 6 (smallest size)',
},
heading5: {
id: 'fabric.editor.heading5',
defaultMessage: 'Heading 5',
description: 'Used for the title of a section of your document, headings run from 1 (largest size) to 6 (smallest size)',
},
heading6: {
id: 'fabric.editor.heading6',
defaultMessage: 'Heading 6',
description: 'Used for the title of a section of your document, headings run from 1 (largest size) to 6 (smallest size)',
},
blockquote: {
id: 'fabric.editor.blockquote',
defaultMessage: 'Quote',
description: 'Quote some text',
},
codeblock: {
id: 'fabric.editor.codeblock',
defaultMessage: 'Code snippet',
description: 'Insert a snippet/segment of code (code block)',
},
panel: {
id: 'fabric.editor.panel',
defaultMessage: 'Panel',
description: 'Visually distinguishes your text by adding a background colour (blue, purple, yellow, green, red)',
},
notePanel: {
id: 'fabric.editor.notePanel',
defaultMessage: 'Note panel',
description: 'Visually distinguishes your text by adding a note panel',
},
successPanel: {
id: 'fabric.editor.successPanel',
defaultMessage: 'Success panel',
description: 'Visually distinguishes your text by adding a success panel',
},
warningPanel: {
id: 'fabric.editor.warningPanel',
defaultMessage: 'Warning panel',
description: 'Visually distinguishes your text by adding a warning panel',
},
errorPanel: {
id: 'fabric.editor.errorPanel',
defaultMessage: 'Error panel',
description: 'Visually distinguishes your text by adding a error panel',
},
other: {
id: 'fabric.editor.other',
defaultMessage: 'Others...',
description: 'Other text formatting',
},
});
var NORMAL_TEXT = {
name: 'normal',
title: messages.normal,
nodeName: 'paragraph',
tagName: 'p',
};
var HEADING_1 = {
name: 'heading1',
title: messages.heading1,
nodeName: 'heading',
tagName: 'h1',
level: 1,
};
var HEADING_2 = {
name: 'heading2',
title: messages.heading2,
nodeName: 'heading',
tagName: 'h2',
level: 2,
};
var HEADING_3 = {
name: 'heading3',
title: messages.heading3,
nodeName: 'heading',
tagName: 'h3',
level: 3,
};
var HEADING_4 = {
name: 'heading4',
title: messages.heading4,
nodeName: 'heading',
tagName: 'h4',
level: 4,
};
var HEADING_5 = {
name: 'heading5',
title: messages.heading5,
nodeName: 'heading',
tagName: 'h5',
level: 5,
};
var HEADING_6 = {
name: 'heading6',
title: messages.heading6,
nodeName: 'heading',
tagName: 'h6',
level: 6,
};
var BLOCK_QUOTE = {
name: 'blockquote',
title: messages.blockquote,
nodeName: 'blockquote',
};
var CODE_BLOCK = {
name: 'codeblock',
title: messages.codeblock,
nodeName: 'codeBlock',
};
var PANEL = {
name: 'panel',
title: messages.panel,
nodeName: 'panel',
};
var OTHER = {
name: 'other',
title: messages.other,
nodeName: '',
};
var TEXT_BLOCK_TYPES = [
NORMAL_TEXT,
HEADING_1,
HEADING_2,
HEADING_3,
HEADING_4,
HEADING_5,
HEADING_6,
];
var WRAPPER_BLOCK_TYPES = [BLOCK_QUOTE, CODE_BLOCK, PANEL];
var HEADINGS_BY_LEVEL = TEXT_BLOCK_TYPES.reduce(function (acc, blockType) {
if (blockType.level && blockType.nodeName === 'heading') {
acc[blockType.level] = blockType;
}
return acc;
}, {});
var HEADINGS_BY_NAME = TEXT_BLOCK_TYPES.reduce(function (acc, blockType) {
if (blockType.level && blockType.nodeName === 'heading') {
acc[blockType.name] = blockType;
}
return acc;
}, {});
function getSelectedWrapperNodes(state) {
var nodes = [];
if (state.selection) {
var _a = state.selection, $from = _a.$from, $to = _a.$to;
var _b = state.schema.nodes, blockquote_1 = _b.blockquote, panel_1 = _b.panel, orderedList_1 = _b.orderedList, bulletList_1 = _b.bulletList, listItem_1 = _b.listItem, codeBlock_1 = _b.codeBlock;
state.doc.nodesBetween($from.pos, $to.pos, function (node, pos) {
if ((node.isBlock &&
[blockquote_1, panel_1, orderedList_1, bulletList_1, listItem_1].indexOf(node.type) >= 0) ||
node.type === codeBlock_1) {
nodes.push(node.type);
}
});
}
return nodes;
}
function defaultInputRuleHandler(inputRule, isBlockNodeRule) {
if (isBlockNodeRule === void 0) { isBlockNodeRule = false; }
var originalHandler = inputRule.handler;
inputRule.handler = function (state, match, start, end) {
// Skip any input rule inside code
// https://product-fabric.atlassian.net/wiki/spaces/E/pages/37945345/Editor+content+feature+rules#Editorcontent/featurerules-Rawtextblocks
var unsupportedMarks = isBlockNodeRule
? hasUnsupportedMarkForBlockInputRule(state, start, end)
: hasUnsupportedMarkForInputRule(state, start, end);
if (state.selection.$from.parent.type.spec.code || unsupportedMarks) {
return;
}
return originalHandler(state, match, start, end);
};
return inputRule;
}
function createInputRule(match, handler, isBlockNodeRule) {
if (isBlockNodeRule === void 0) { isBlockNodeRule = false; }
return defaultInputRuleHandler(new InputRule(match, handler), isBlockNodeRule);
}
// ProseMirror uses the Unicode Character 'OBJECT REPLACEMENT CHARACTER' (U+FFFC) as text representation for
// leaf nodes, i.e. nodes that don't have any content or text property (e.g. hardBreak, emoji, mention, rule)
// It was introduced because of https://github.com/ProseMirror/prosemirror/issues/262
// This can be used in an input rule regex to be able to include or exclude such nodes.
var leafNodeReplacementCharacter = '\ufffc';
var hasUnsupportedMarkForBlockInputRule = function (state, start, end) {
var doc = state.doc, marks = state.schema.marks;
var unsupportedMarksPresent = false;
var isUnsupportedMark = function (node) {
return node.type === marks.code ||
node.type === marks.link ||
node.type === marks.typeAheadQuery;
};
doc.nodesBetween(start, end, function (node) {
unsupportedMarksPresent =
unsupportedMarksPresent ||
node.marks.filter(isUnsupportedMark).length > 0;
});
return unsupportedMarksPresent;
};
var hasUnsupportedMarkForInputRule = function (state, start, end) {
var doc = state.doc, marks = state.schema.marks;
var unsupportedMarksPresent = false;
var isCodemark = function (node) {
return node.type === marks.code || node.type === marks.typeAheadQuery;
};
doc.nodesBetween(start, end, function (node) {
unsupportedMarksPresent =
unsupportedMarksPresent || node.marks.filter(isCodemark).length > 0;
});
return unsupportedMarksPresent;
};
/**
* A helper to get the underlying array of a fragment.
*/
function getFragmentBackingArray(fragment) {
return fragment.content;
}
function mapFragment(content, callback, parent) {
if (parent === void 0) { parent = null; }
var children = [];
for (var i = 0, size = content.childCount; i < size; i++) {
var node = content.child(i);
var transformed = node.isLeaf
? callback(node, parent, i)
: callback(node.copy(mapFragment(node.content, callback, node)), parent, i);
if (transformed) {
if (transformed instanceof Fragment) {
children.push.apply(children, __spread(getFragmentBackingArray(transformed)));
}
else if (Array.isArray(transformed)) {
children.push.apply(children, __spread(transformed));
}
else {
children.push(transformed);
}
}
}
return Fragment.fromArray(children);
}
function mapSlice(slice, callback) {
var fragment = mapFragment(slice.content, callback);
return new Slice(fragment, slice.openStart, slice.openEnd);
}
function setNormalText() {
return function (state, dispatch) {
var tr = state.tr, _a = state.selection, $from = _a.$from, $to = _a.$to, schema = state.schema;
if (dispatch) {
dispatch(tr.setBlockType($from.pos, $to.pos, schema.nodes.paragraph));
}
return true;
};
}
function setHeading(level) {
return function (state, dispatch) {
var tr = state.tr, _a = state.selection, $from = _a.$from, $to = _a.$to, schema = state.schema;
if (dispatch) {
dispatch(tr.setBlockType($from.pos, $to.pos, schema.nodes.heading, { level: level }));
}
return true;
};
}
function setBlockType(name) {
return function (state, dispatch) {
var nodes = state.schema.nodes;
if (name === NORMAL_TEXT.name && nodes.paragraph) {
return setNormalText()(state, dispatch);
}
var headingBlockType = HEADINGS_BY_NAME[name];
if (headingBlockType && nodes.heading && headingBlockType.level) {
return setHeading(headingBlockType.level)(state, dispatch);
}
return false;
};
}
function transformToCodeBlockAction(state, attrs) {
var codeBlock = state.schema.nodes.codeBlock;
if (state.selection.empty) {
var startOfCodeBlockText = state.selection.$from;
var parentPos = startOfCodeBlockText.before();
var end = startOfCodeBlockText.end();
var slice = mapSlice(state.doc.slice(startOfCodeBlockText.pos, end), function (node) {
if (node.type === state.schema.nodes.hardBreak) {
return state.schema.text('\n');
}
if (node.isText) {
return node.mark([]);
}
else if (node.isInline) {
return node.attrs.text ? state.schema.text(node.attrs.text) : null;
}
else {
return node.content.childCount ? node.content : null;
}
});
var tr = state.tr.replaceRange(startOfCodeBlockText.pos, end, slice);
// If our offset isnt at 3 (backticks) at the start of line, cater for content.
if (startOfCodeBlockText.parentOffset >= 3) {
return tr.split(startOfCodeBlockText.pos, undefined, [
{ type: codeBlock, attrs: attrs },
]);
}
return tr.setNodeMarkup(parentPos, codeBlock, attrs);
}
return state.tr;
}
function isConvertableToCodeBlock(state) {
// Before a document is loaded, there is no selection.
if (!state.selection) {
return false;
}
var $from = state.selection.$from;
var node = $from.parent;
if (!node.isTextblock || node.type === state.schema.nodes.codeBlock) {
return false;
}
var parentDepth = $from.depth - 1;
var parentNode = $from.node(parentDepth);
var index = $from.index(parentDepth);
return parentNode.canReplaceWith(index, index + 1, state.schema.nodes.codeBlock);
}
/**
* Function will insert code block at current selection if block is empty or below current selection and set focus on it.
*/
function insertCodeBlock() {
return function (state, dispatch) {
var tr = state.tr;
var $to = state.selection.$to;
var codeBlock = state.schema.nodes.codeBlock;
var getNextNode = state.doc.nodeAt($to.pos + 1);
var addPos = getNextNode && getNextNode.isText ? 0 : 1;
/** We always want to append a block type */
tr.replaceRangeWith($to.pos + addPos, $to.pos + addPos, codeBlock.createAndFill());
tr.setSelection(Selection.near(tr.doc.resolve(state.selection.to + addPos)));
if (dispatch) {