Compare commits
1 commit
develop
...
feat/artic
Author | SHA1 | Date | |
---|---|---|---|
|
bacab4d6a4 |
10 changed files with 1386 additions and 3 deletions
|
@ -0,0 +1,202 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="editor-root">
|
||||||
|
<div ref="editor" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { EditorView } from 'prosemirror-view';
|
||||||
|
|
||||||
|
import { schema, defaultMarkdownSerializer } from 'prosemirror-markdown';
|
||||||
|
|
||||||
|
import { EditorState, Selection } from 'prosemirror-state';
|
||||||
|
import { defaultMarkdownParser } from 'prosemirror-markdown';
|
||||||
|
import { wootWriterSetup } from '@chatwoot/prosemirror-schema';
|
||||||
|
import { buildMenuItems } from './src/menu';
|
||||||
|
import marksFormattingRules from './src/marksInputRules';
|
||||||
|
import blockFormattingRules from './src/blockInputRules';
|
||||||
|
|
||||||
|
import '@chatwoot/prosemirror-schema/src/woot-editor.css';
|
||||||
|
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||||
|
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||||
|
|
||||||
|
const createState = (content, placeholder, plugins = []) => {
|
||||||
|
return EditorState.create({
|
||||||
|
doc: defaultMarkdownParser.parse(content),
|
||||||
|
plugins: wootWriterSetup({
|
||||||
|
schema,
|
||||||
|
placeholder,
|
||||||
|
plugins,
|
||||||
|
menuContent: buildMenuItems(schema).fullMenu,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WootMessageEditor',
|
||||||
|
mixins: [eventListenerMixins, uiSettingsMixin],
|
||||||
|
props: {
|
||||||
|
value: { type: String, default: '' },
|
||||||
|
editorId: { type: String, default: '' },
|
||||||
|
placeholder: { type: String, default: '' },
|
||||||
|
isPrivate: { type: Boolean, default: false },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showCannedMenu: false,
|
||||||
|
mentionSearchKey: '',
|
||||||
|
cannedSearchTerm: '',
|
||||||
|
editorView: null,
|
||||||
|
range: null,
|
||||||
|
state: undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
contentFromEditor() {
|
||||||
|
return defaultMarkdownSerializer.serialize(this.editorView.state.doc);
|
||||||
|
},
|
||||||
|
plugins() {
|
||||||
|
return [blockFormattingRules(schema), marksFormattingRules(schema)];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(newValue = '') {
|
||||||
|
if (newValue !== this.contentFromEditor) {
|
||||||
|
this.reloadState();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editorId() {
|
||||||
|
this.reloadState();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.state = createState(this.value, this.placeholder, this.plugins);
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.createEditorView();
|
||||||
|
this.editorView.updateState(this.state);
|
||||||
|
this.focusEditorInputField();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addBold() {
|
||||||
|
const { state, dispatch } = this.editorView;
|
||||||
|
const { tr } = state;
|
||||||
|
tr.insertText('**bold**');
|
||||||
|
dispatch(tr);
|
||||||
|
},
|
||||||
|
reloadState() {
|
||||||
|
this.state = createState(this.value, this.placeholder, this.plugins);
|
||||||
|
this.editorView.updateState(this.state);
|
||||||
|
this.focusEditorInputField();
|
||||||
|
},
|
||||||
|
createEditorView() {
|
||||||
|
this.editorView = new EditorView(this.$refs.editor, {
|
||||||
|
state: this.state,
|
||||||
|
dispatchTransaction: tx => {
|
||||||
|
this.state = this.state.apply(tx);
|
||||||
|
this.emitOnChange();
|
||||||
|
},
|
||||||
|
handleDOMEvents: {
|
||||||
|
keyup: () => {
|
||||||
|
this.onKeyup();
|
||||||
|
},
|
||||||
|
keydown: (view, event) => {
|
||||||
|
this.onKeydown(event);
|
||||||
|
},
|
||||||
|
focus: () => {
|
||||||
|
this.onFocus();
|
||||||
|
},
|
||||||
|
blur: () => {
|
||||||
|
this.onBlur();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleKeyEvents() {},
|
||||||
|
focusEditorInputField() {
|
||||||
|
const { tr } = this.editorView.state;
|
||||||
|
const selection = Selection.atEnd(tr.doc);
|
||||||
|
|
||||||
|
this.editorView.dispatch(tr.setSelection(selection));
|
||||||
|
this.editorView.focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
emitOnChange() {
|
||||||
|
this.editorView.updateState(this.state);
|
||||||
|
|
||||||
|
this.$emit('input', this.contentFromEditor);
|
||||||
|
},
|
||||||
|
|
||||||
|
onKeyup() {},
|
||||||
|
onKeydown() {},
|
||||||
|
onBlur() {
|
||||||
|
this.$emit('blur');
|
||||||
|
},
|
||||||
|
onFocus() {
|
||||||
|
this.$emit('focus');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ProseMirror-menubar-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> .ProseMirror {
|
||||||
|
padding: 0;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-root {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ProseMirror-woot-style {
|
||||||
|
min-height: 8rem;
|
||||||
|
max-height: 12rem;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ProseMirror-prompt {
|
||||||
|
z-index: var(--z-index-highest);
|
||||||
|
background: var(--color-background-light);
|
||||||
|
border-radius: var(--border-radius-normal);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-private {
|
||||||
|
.prosemirror-mention-node {
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
background: var(--s-50);
|
||||||
|
color: var(--s-900);
|
||||||
|
padding: 0 var(--space-smaller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-wrap {
|
||||||
|
margin-bottom: var(--space-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-editor {
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius-normal);
|
||||||
|
padding: 0 var(--space-slab);
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor_warning {
|
||||||
|
border: 1px solid var(--r-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-warning__message {
|
||||||
|
color: var(--r-400);
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
padding: var(--space-smaller) 0 0 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,176 @@
|
||||||
|
import {
|
||||||
|
textblockTypeInputRule,
|
||||||
|
wrappingInputRule,
|
||||||
|
inputRules,
|
||||||
|
} from 'prosemirror-inputrules';
|
||||||
|
|
||||||
|
import { leafNodeReplacementCharacter } from './utils';
|
||||||
|
import { createInputRule, defaultInputRuleHandler } from './utils';
|
||||||
|
import {
|
||||||
|
isConvertableToCodeBlock,
|
||||||
|
transformToCodeBlockAction,
|
||||||
|
insertBlock,
|
||||||
|
} from './commands';
|
||||||
|
import { safeInsert } from 'prosemirror-utils';
|
||||||
|
|
||||||
|
const MAX_HEADING_LEVEL = 5;
|
||||||
|
|
||||||
|
function getHeadingLevel(match) {
|
||||||
|
return {
|
||||||
|
level: match[1].length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function headingRule(nodeType, maxLevel) {
|
||||||
|
return textblockTypeInputRule(
|
||||||
|
new RegExp('^(#{1,' + maxLevel + '})\\s$'),
|
||||||
|
nodeType,
|
||||||
|
getHeadingLevel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function blockQuoteRule(nodeType) {
|
||||||
|
return wrappingInputRule(/^\s*>\s$/, nodeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function codeBlockRule(nodeType) {
|
||||||
|
return textblockTypeInputRule(/^```$/, nodeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get heading rules
|
||||||
|
*
|
||||||
|
* @param {Schema} schema
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
function getHeadingRules(schema) {
|
||||||
|
// '# ' for h1, '## ' for h2 and etc
|
||||||
|
const hashRule = defaultInputRuleHandler(
|
||||||
|
headingRule(schema.nodes.heading, MAX_HEADING_LEVEL),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const leftNodeReplacementHashRule = createInputRule(
|
||||||
|
new RegExp(`${leafNodeReplacementCharacter}(#{1,6})\\s$`),
|
||||||
|
(state, match, start, end) => {
|
||||||
|
const level = match[1].length;
|
||||||
|
return insertBlock(
|
||||||
|
state,
|
||||||
|
schema.nodes.heading,
|
||||||
|
`heading${level}`,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
{ level }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
return [hashRule, leftNodeReplacementHashRule];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all block quote input rules
|
||||||
|
*
|
||||||
|
* @param {Schema} schema
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
function getBlockQuoteRules(schema) {
|
||||||
|
// '> ' for blockquote
|
||||||
|
const greatherThanRule = defaultInputRuleHandler(
|
||||||
|
blockQuoteRule(schema.nodes.blockquote),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const leftNodeReplacementGreatherRule = createInputRule(
|
||||||
|
new RegExp(`${leafNodeReplacementCharacter}\\s*>\\s$`),
|
||||||
|
(state, _match, start, end) => {
|
||||||
|
return insertBlock(
|
||||||
|
state,
|
||||||
|
schema.nodes.blockquote,
|
||||||
|
'blockquote',
|
||||||
|
start,
|
||||||
|
end
|
||||||
|
);
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
return [greatherThanRule, leftNodeReplacementGreatherRule];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all code block input rules
|
||||||
|
*
|
||||||
|
* @param {Schema} schema
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
function getCodeBlockRules(schema) {
|
||||||
|
const threeTildeRule = createInputRule(
|
||||||
|
/((^`{3,})|(\s`{3,}))(\S*)$/,
|
||||||
|
(state, match, start, end) => {
|
||||||
|
const attributes = {};
|
||||||
|
if (match[4]) {
|
||||||
|
attributes.language = match[4];
|
||||||
|
}
|
||||||
|
const newStart = match[0][0] === ' ' ? start + 1 : start;
|
||||||
|
if (isConvertableToCodeBlock(state)) {
|
||||||
|
const tr = transformToCodeBlockAction(state, attributes)
|
||||||
|
// remove markdown decorator ```
|
||||||
|
.delete(newStart, end)
|
||||||
|
.scrollIntoView();
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
let { tr } = state;
|
||||||
|
tr = tr.delete(newStart, end);
|
||||||
|
const codeBlock = state.schema.nodes.code_block.createChecked();
|
||||||
|
return safeInsert(codeBlock)(tr);
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const leftNodeReplacementThreeTildeRule = createInputRule(
|
||||||
|
new RegExp(`((${leafNodeReplacementCharacter}\`{3,})|(\\s\`{3,}))(\\S*)$`),
|
||||||
|
(state, match, start, end) => {
|
||||||
|
const attributes = {};
|
||||||
|
if (match[4]) {
|
||||||
|
attributes.language = match[4];
|
||||||
|
}
|
||||||
|
let tr = insertBlock(
|
||||||
|
state,
|
||||||
|
schema.nodes.code_block,
|
||||||
|
'codeblock',
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
attributes
|
||||||
|
);
|
||||||
|
return tr;
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
return [threeTildeRule, leftNodeReplacementThreeTildeRule];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function blocksInputRule(schema) {
|
||||||
|
const rules = [];
|
||||||
|
|
||||||
|
if (schema.nodes.heading) {
|
||||||
|
rules.push(...getHeadingRules(schema));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.nodes.blockquote) {
|
||||||
|
rules.push(...getBlockQuoteRules(schema));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.nodes.code_block) {
|
||||||
|
rules.push(...getCodeBlockRules(schema));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rules.length !== 0) {
|
||||||
|
return inputRules({ rules });
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default blocksInputRule;
|
|
@ -0,0 +1,157 @@
|
||||||
|
import { hasParentNodeOfType } from 'prosemirror-utils';
|
||||||
|
import { TextSelection, NodeSelection } from 'prosemirror-state';
|
||||||
|
import { mapSlice } from './utils';
|
||||||
|
|
||||||
|
export const applyMarkOnRange = (from, to, removeMark, mark, tr) => {
|
||||||
|
// const { schema } = tr.doc.type;
|
||||||
|
// const { code } = schema.marks;
|
||||||
|
// if (mark.type === code) {
|
||||||
|
// // When turning to code we need to flat some special characters
|
||||||
|
// import { transformSmartCharsMentionsAndEmojis } from '../plugins/text-formatting/commands/transform-to-code';
|
||||||
|
// transformSmartCharsMentionsAndEmojis(from, to, tr);
|
||||||
|
// }
|
||||||
|
|
||||||
|
tr.doc.nodesBetween(tr.mapping.map(from), tr.mapping.map(to), (node, pos) => {
|
||||||
|
if (!node.isText) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an issue when the user selects some text.
|
||||||
|
// We need to check if the current node position is less than the range selection from.
|
||||||
|
// If it’s true, that means we should apply the mark using the range selection,
|
||||||
|
// not the current node position.
|
||||||
|
const nodeBetweenFrom = Math.max(pos, tr.mapping.map(from));
|
||||||
|
const nodeBetweenTo = Math.min(pos + node.nodeSize, tr.mapping.map(to));
|
||||||
|
|
||||||
|
if (removeMark) {
|
||||||
|
tr.removeMark(nodeBetweenFrom, nodeBetweenTo, mark);
|
||||||
|
} else {
|
||||||
|
tr.addMark(nodeBetweenFrom, nodeBetweenTo, mark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return tr;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const insertBlock = (state, nodeType, nodeName, start, end, attrs) => {
|
||||||
|
// To ensure that match is done after HardBreak.
|
||||||
|
const { hardBreak, codeBlock, listItem } = state.schema.nodes;
|
||||||
|
const $pos = state.doc.resolve(start);
|
||||||
|
if ($pos.nodeAfter.type !== hardBreak) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To ensure no nesting is done. (unless we're inserting a codeBlock inside lists)
|
||||||
|
if (
|
||||||
|
$pos.depth > 1 &&
|
||||||
|
!(nodeType === codeBlock && hasParentNodeOfType(listItem)(state.selection))
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split at the start of autoformatting and delete formatting characters.
|
||||||
|
let tr = state.tr.delete(start, end).split(start);
|
||||||
|
let currentNode = tr.doc.nodeAt(start + 1);
|
||||||
|
|
||||||
|
// If node has more content split at the end of autoformatting.
|
||||||
|
let nodeHasMoreContent = false;
|
||||||
|
tr.doc.nodesBetween(start, start + currentNode.nodeSize, (node, pos) => {
|
||||||
|
if (!nodeHasMoreContent && node.type === hardBreak) {
|
||||||
|
nodeHasMoreContent = true;
|
||||||
|
tr = tr.split(pos + 1).delete(pos, pos + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (nodeHasMoreContent) {
|
||||||
|
currentNode = tr.doc.nodeAt(start + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new node and fill with content of current node.
|
||||||
|
const { blockquote, paragraph } = state.schema.nodes;
|
||||||
|
let content;
|
||||||
|
let depth;
|
||||||
|
if (nodeType === blockquote) {
|
||||||
|
depth = 3;
|
||||||
|
content = [paragraph.create({}, currentNode.content)];
|
||||||
|
} else {
|
||||||
|
depth = 2;
|
||||||
|
content = currentNode.content;
|
||||||
|
}
|
||||||
|
const newNode = nodeType.create(attrs, content);
|
||||||
|
|
||||||
|
// Add new node.
|
||||||
|
tr = tr
|
||||||
|
.setSelection(new NodeSelection(tr.doc.resolve(start + 1)))
|
||||||
|
.replaceSelectionWith(newNode)
|
||||||
|
.setSelection(new TextSelection(tr.doc.resolve(start + depth)));
|
||||||
|
return tr;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function transformToCodeBlockAction(state, attrs) {
|
||||||
|
if (!state.selection.empty) {
|
||||||
|
// Don't do anything, if there is something selected
|
||||||
|
return state.tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeBlock = state.schema.nodes.code_block;
|
||||||
|
const startOfCodeBlockText = state.selection.$from;
|
||||||
|
const parentPos = startOfCodeBlockText.before();
|
||||||
|
const end = startOfCodeBlockText.end();
|
||||||
|
|
||||||
|
const codeBlockSlice = mapSlice(
|
||||||
|
state.doc.slice(startOfCodeBlockText.pos, end),
|
||||||
|
node => {
|
||||||
|
if (node.type === state.schema.nodes.hard_break) {
|
||||||
|
return state.schema.text('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.isText) {
|
||||||
|
return node.mark([]);
|
||||||
|
}
|
||||||
|
if (node.isInline) {
|
||||||
|
return node.attrs.text ? state.schema.text(node.attrs.text) : null;
|
||||||
|
}
|
||||||
|
return node.content.childCount ? node.content : null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const tr = state.tr.replaceRange(
|
||||||
|
startOfCodeBlockText.pos,
|
||||||
|
end,
|
||||||
|
codeBlockSlice
|
||||||
|
);
|
||||||
|
// 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 },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
// TODO: Check parent node for valid code block marks, ATM It's not necessary because code block doesn't have any valid mark.
|
||||||
|
const codeBlockMarks = [];
|
||||||
|
return tr.setNodeMarkup(parentPos, codeBlock, attrs, codeBlockMarks);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isConvertableToCodeBlock(state) {
|
||||||
|
// Before a document is loaded, there is no selection.
|
||||||
|
if (!state.selection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { $from } = state.selection;
|
||||||
|
const node = $from.parent;
|
||||||
|
|
||||||
|
if (!node.isTextblock || node.type === state.schema.nodes.code_block) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentDepth = $from.depth - 1;
|
||||||
|
const parentNode = $from.node(parentDepth);
|
||||||
|
const index = $from.index(parentDepth);
|
||||||
|
|
||||||
|
return parentNode.canReplaceWith(
|
||||||
|
index,
|
||||||
|
index + 1,
|
||||||
|
state.schema.nodes.code_block
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
const BaseIcon = {
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
fill: 'none',
|
||||||
|
stroke: 'currentColor',
|
||||||
|
strokeWidth: 2,
|
||||||
|
strokeLinecap: 'round',
|
||||||
|
strokeLinejoin: 'round',
|
||||||
|
path: '',
|
||||||
|
};
|
||||||
|
export const BoldIcon = {
|
||||||
|
...BaseIcon,
|
||||||
|
path:
|
||||||
|
'M6.935 4.44A1.5 1.5 0 0 1 7.996 4h4.383C15.017 4 17 6.182 17 8.625a4.63 4.63 0 0 1-.865 2.682c1.077.827 1.866 2.12 1.866 3.813C18 18.232 15.3 20 13.12 20H8a1.5 1.5 0 0 1-1.5-1.5l-.004-13c0-.397.158-.779.44-1.06ZM9.5 10.25h2.88c.903 0 1.62-.76 1.62-1.625S13.281 7 12.38 7H9.498l.002 3.25Zm0 3V17h3.62c.874 0 1.88-.754 1.88-1.88 0-1.13-.974-1.87-1.88-1.87H9.5Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ItalicsIcon = {
|
||||||
|
...BaseIcon,
|
||||||
|
path:
|
||||||
|
'M9.75 4h8.504a.75.75 0 0 1 .102 1.493l-.102.006h-3.197L10.037 18.5h4.213a.75.75 0 0 1 .742.648l.007.102a.75.75 0 0 1-.648.743L14.25 20h-9.5a.747.747 0 0 1-.746-.75c0-.38.28-.694.645-.743l.101-.007h3.685l.021-.065L13.45 5.499h-3.7a.75.75 0 0 1-.742-.648L9 4.75a.75.75 0 0 1 .648-.743L9.751 4h8.503-8.503Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CodeIcon = {
|
||||||
|
...BaseIcon,
|
||||||
|
path:
|
||||||
|
'm8.066 18.943 6.5-14.5a.75.75 0 0 1 1.404.518l-.036.096-6.5 14.5a.75.75 0 0 1-1.404-.518l.036-.096 6.5-14.5-6.5 14.5ZM2.22 11.47l4.25-4.25a.75.75 0 0 1 1.133.976l-.073.085L3.81 12l3.72 3.719a.75.75 0 0 1-.976 1.133l-.084-.073-4.25-4.25a.75.75 0 0 1-.073-.976l.073-.084 4.25-4.25-4.25 4.25Zm14.25-4.25a.75.75 0 0 1 .976-.073l.084.073 4.25 4.25a.75.75 0 0 1 .073.976l-.073.085-4.25 4.25a.75.75 0 0 1-1.133-.977l.073-.084L20.19 12l-3.72-3.72a.75.75 0 0 1 0-1.06Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LinkIcon = {
|
||||||
|
...BaseIcon,
|
||||||
|
path:
|
||||||
|
'M9.25 7a.75.75 0 0 1 .11 1.492l-.11.008H7a3.5 3.5 0 0 0-.206 6.994L7 15.5h2.25a.75.75 0 0 1 .11 1.492L9.25 17H7a5 5 0 0 1-.25-9.994L7 7h2.25ZM17 7a5 5 0 0 1 .25 9.994L17 17h-2.25a.75.75 0 0 1-.11-1.492l.11-.008H17a3.5 3.5 0 0 0 .206-6.994L17 8.5h-2.25a.75.75 0 0 1-.11-1.492L14.75 7H17ZM7 11.25h10a.75.75 0 0 1 .102 1.493L17 12.75H7a.75.75 0 0 1-.102-1.493L7 11.25h10H7Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UndoIcon = {
|
||||||
|
...BaseIcon,
|
||||||
|
path:
|
||||||
|
'M4.75 2a.75.75 0 0 1 .743.648l.007.102v5.69l4.574-4.56a6.41 6.41 0 0 1 8.879-.179l.186.18a6.41 6.41 0 0 1 0 9.063l-8.846 8.84a.75.75 0 0 1-1.06-1.062l8.845-8.838a4.91 4.91 0 0 0-6.766-7.112l-.178.17L6.562 9.5h5.688a.75.75 0 0 1 .743.648l.007.102a.75.75 0 0 1-.648.743L12.25 11h-7.5a.75.75 0 0 1-.743-.648L4 10.25v-7.5A.75.75 0 0 1 4.75 2Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RedoIcon = {
|
||||||
|
...BaseIcon,
|
||||||
|
path:
|
||||||
|
'M19.25 2a.75.75 0 0 0-.743.648l-.007.102v5.69l-4.574-4.56a6.41 6.41 0 0 0-8.878-.179l-.186.18a6.41 6.41 0 0 0 0 9.063l8.845 8.84a.75.75 0 0 0 1.06-1.062l-8.845-8.838a4.91 4.91 0 0 1 6.766-7.112l.178.17L17.438 9.5H11.75a.75.75 0 0 0-.743.648L11 10.25c0 .38.282.694.648.743l.102.007h7.5a.75.75 0 0 0 .743-.648L20 10.25v-7.5a.75.75 0 0 0-.75-.75Z',
|
||||||
|
};
|
||||||
|
export const BulletListIcon = {
|
||||||
|
...BaseIcon,
|
||||||
|
path:
|
||||||
|
'M3.25 17.5a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm3.5.5h14.5a.75.75 0 0 1 .102 1.494l-.102.006H6.75a.75.75 0 0 1-.102-1.493L6.75 18h14.5-14.5Zm-3.5-7a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm3.5.5h14.5a.75.75 0 0 1 .102 1.494L21.25 13H6.75a.75.75 0 0 1-.102-1.493l.102-.007h14.5-14.5Zm-3.5-7A1.25 1.25 0 1 1 3.25 7a1.25 1.25 0 0 1 0-2.499Zm3.5.5h14.5a.75.75 0 0 1 .102 1.494l-.102.006H6.75a.75.75 0 0 1-.102-1.493L6.75 5h14.5-14.5Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TextNumberListIcon = {
|
||||||
|
...BaseIcon,
|
||||||
|
path:
|
||||||
|
'M6 2.75a.75.75 0 0 0-1.434-.307l-.002.003a1.45 1.45 0 0 1-.067.132 4.126 4.126 0 0 1-.238.384c-.217.313-.524.663-.906.902a.75.75 0 1 0 .794 1.272c.125-.078.243-.161.353-.248V7.25a.75.75 0 0 0 1.5 0v-4.5ZM20.5 18.75a.75.75 0 0 0-.75-.75h-9a.75.75 0 0 0 0 1.5h9a.75.75 0 0 0 .75-.75ZM20.5 12.244a.75.75 0 0 0-.75-.75h-9a.75.75 0 1 0 0 1.5h9a.75.75 0 0 0 .75-.75ZM20.5 5.75a.75.75 0 0 0-.75-.75h-9a.75.75 0 0 0 0 1.5h9a.75.75 0 0 0 .75-.75ZM5.15 10.52c-.3-.053-.676.066-.87.26a.75.75 0 1 1-1.06-1.06c.556-.556 1.43-.812 2.192-.677.397.07.805.254 1.115.605.316.358.473.825.473 1.352 0 .62-.271 1.08-.606 1.42-.278.283-.63.511-.906.689l-.08.051a5.88 5.88 0 0 0-.481.34H6.25a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75c0-1.314.984-1.953 1.575-2.337l.06-.04c.318-.205.533-.345.69-.504.134-.136.175-.238.175-.369 0-.223-.061-.318-.098-.36a.42.42 0 0 0-.251-.12ZM2.97 21.28s.093.084.004.005l.006.005.013.013a1.426 1.426 0 0 0 .15.125c.095.07.227.158.397.243.341.17.83.329 1.46.329.64 0 1.196-.181 1.601-.54.408-.36.61-.857.595-1.359A1.775 1.775 0 0 0 6.77 19c.259-.305.412-.685.426-1.101a1.73 1.73 0 0 0-.595-1.36C6.196 16.181 5.64 16 5 16c-.63 0-1.119.158-1.46.33a2.592 2.592 0 0 0-.51.334 1.426 1.426 0 0 0-.037.033l-.013.013-.006.005-.002.003H2.97l-.001.002a.75.75 0 0 0 1.048 1.072 1.1 1.1 0 0 1 .192-.121c.159-.08.42-.171.79-.171.36 0 .536.1.608.164.07.061.09.127.088.187a.325.325 0 0 1-.123.23c-.089.077-.263.169-.573.169a.75.75 0 0 0 0 1.5c.31 0 .484.092.573.168.091.08.121.166.123.231a.232.232 0 0 1-.088.187c-.072.064-.247.164-.608.164a1.75 1.75 0 0 1-.79-.17 1.1 1.1 0 0 1-.192-.122.75.75 0 0 0-1.048 1.072Zm.002-4.563-.001.002c.007-.006.2-.168 0-.002Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Heading1Icon = {
|
||||||
|
...BaseIcon,
|
||||||
|
path:
|
||||||
|
'M19.59 5.081a.746.746 0 0 0-.809.084.751.751 0 0 0-.249.367c-.69 2.051-2.057 3.409-3.168 4.075a.75.75 0 0 0 .772 1.286c.774-.464 1.623-1.18 2.364-2.146v9.503a.75.75 0 0 0 1.5 0V5.772a.75.75 0 0 0-.41-.69ZM3.5 5.75a.75.75 0 0 0-1.5 0v12.5a.75.75 0 0 0 1.5 0V12.5H10v5.75a.75.75 0 0 0 1.5 0V5.75a.75.75 0 0 0-1.5 0V11H3.5V5.75Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Heading2Icon = {
|
||||||
|
...BaseIcon,
|
||||||
|
path:
|
||||||
|
'M4.5 5.75a.75.75 0 0 0-1.5 0v12.5a.75.75 0 0 0 1.5 0V12.5H11v5.75a.75.75 0 0 0 1.5 0V5.75a.75.75 0 0 0-1.5 0V11H4.5V5.75Zm10.921 2.085c.23-.46.913-1.335 2.58-1.335.842 0 1.459.26 1.86.639.397.376.64.921.64 1.611 0 1.963-1.3 3.068-2.958 4.343l-.212.163C15.825 14.409 14 15.806 14 18.25a.75.75 0 0 0 .75.75h6.5a.75.75 0 0 0 0-1.5h-5.66c.315-1.252 1.427-2.11 2.866-3.218C20.05 13.057 22 11.537 22 8.75c0-1.06-.383-2.015-1.11-2.702C20.166 5.364 19.158 5 18 5c-2.333 0-3.484 1.291-3.92 2.165a.75.75 0 0 0 1.341.67Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Heading3Icon = {
|
||||||
|
...BaseIcon,
|
||||||
|
path:
|
||||||
|
'M3.5 5.75a.75.75 0 0 0-1.5 0v12.5a.75.75 0 0 0 1.5 0V12.5H10v5.75a.75.75 0 0 0 1.5 0V5.75a.75.75 0 0 0-1.5 0V11H3.5V5.75Zm11.92 2.085c.23-.46.914-1.335 2.58-1.335.843 0 1.46.26 1.86.639.398.376.64.921.64 1.611 0 .606-.161 1.026-.384 1.332-.228.314-.555.554-.953.735-.816.37-1.802.433-2.383.433a.75.75 0 0 0 0 1.5c.581 0 1.567.063 2.383.433.398.18.725.42.953.735.223.306.384.726.384 1.332 0 1.086-.914 2.25-2.5 2.25-1.727 0-2.348-.76-2.553-1.276a.75.75 0 1 0-1.394.552C14.508 17.926 15.727 19 18 19c2.414 0 4-1.836 4-3.75 0-.894-.245-1.63-.67-2.214A3.679 3.679 0 0 0 20.144 12a3.679 3.679 0 0 0 1.186-1.036c.425-.584.67-1.32.67-2.214 0-1.06-.383-2.015-1.11-2.702C20.165 5.364 19.157 5 18 5c-2.334 0-3.484 1.291-3.92 2.165a.75.75 0 1 0 1.34.67Z',
|
||||||
|
};
|
|
@ -0,0 +1,248 @@
|
||||||
|
/* eslint-disable no-useless-escape */
|
||||||
|
import { inputRules } from 'prosemirror-inputrules';
|
||||||
|
import { applyMarkOnRange } from './commands';
|
||||||
|
import { createInputRule } from './utils';
|
||||||
|
|
||||||
|
const validCombos = {
|
||||||
|
'**': ['_', '~~'],
|
||||||
|
'*': ['__', '~~'],
|
||||||
|
__: ['*', '~~'],
|
||||||
|
_: ['**', '~~'],
|
||||||
|
'~~': ['__', '_', '**', '*'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const validRegex = (char, str) => {
|
||||||
|
for (let i = 0; i < validCombos[char].length; i += 1) {
|
||||||
|
const ch = validCombos[char][i];
|
||||||
|
if (ch === str) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const matchLength = str.length - ch.length;
|
||||||
|
if (str.substr(matchLength, str.length) === ch) {
|
||||||
|
return validRegex(ch, str.substr(0, matchLength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function addMark(markType, schema, charSize, char) {
|
||||||
|
return (state, match, start, end) => {
|
||||||
|
const [, prefix, textWithCombo] = match;
|
||||||
|
const to = end;
|
||||||
|
// in case of *string* pattern it matches the text from beginning of the paragraph,
|
||||||
|
// because we want ** to work for strong text
|
||||||
|
// that's why "start" argument is wrong and we need to calculate it ourselves
|
||||||
|
const from = textWithCombo ? start + prefix.length : start;
|
||||||
|
const nodeBefore = state.doc.resolve(start + prefix.length).nodeBefore;
|
||||||
|
|
||||||
|
if (
|
||||||
|
prefix &&
|
||||||
|
prefix.length > 0 &&
|
||||||
|
!validRegex(char, prefix) &&
|
||||||
|
!(nodeBefore && nodeBefore.type === state.schema.nodes.hard_break)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// fixes the following case: my `*name` is *
|
||||||
|
// expected result: should ignore special characters inside "code"
|
||||||
|
if (
|
||||||
|
state.schema.marks.code &&
|
||||||
|
state.schema.marks.code.isInSet(state.doc.resolve(from + 1).marks())
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent autoformatting across hardbreaks
|
||||||
|
let containsHardBreak;
|
||||||
|
state.doc.nodesBetween(from, to, node => {
|
||||||
|
if (node.type === schema.nodes.hard_break) {
|
||||||
|
containsHardBreak = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !containsHardBreak;
|
||||||
|
});
|
||||||
|
if (containsHardBreak) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixes autoformatting in heading nodes: # Heading *bold*
|
||||||
|
// expected result: should not autoformat *bold*; <h1>Heading *bold*</h1>
|
||||||
|
if (state.doc.resolve(from).sameParent(state.doc.resolve(to))) {
|
||||||
|
if (!state.doc.resolve(from).parent.type.allowsMarkType(markType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply mark to the range (from, to)
|
||||||
|
let tr = state.tr.addMark(from, to, markType.create());
|
||||||
|
|
||||||
|
if (charSize > 1) {
|
||||||
|
// delete special characters after the text
|
||||||
|
// Prosemirror removes the last symbol by itself, so we need to remove "charSize - 1" symbols
|
||||||
|
tr = tr.delete(to - (charSize - 1), to);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
tr
|
||||||
|
// delete special characters before the text
|
||||||
|
.delete(from, from + charSize)
|
||||||
|
.removeStoredMark(markType)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCodeMark(markType, specialChar) {
|
||||||
|
return (state, match, start, end) => {
|
||||||
|
if (match[1] && match[1].length > 0) {
|
||||||
|
const allowedPrefixConditions = [
|
||||||
|
prefix => {
|
||||||
|
return prefix === '(';
|
||||||
|
},
|
||||||
|
prefix => {
|
||||||
|
const nodeBefore = state.doc.resolve(start + prefix.length)
|
||||||
|
.nodeBefore;
|
||||||
|
return (
|
||||||
|
(nodeBefore && nodeBefore.type === state.schema.nodes.hard_break) ||
|
||||||
|
false
|
||||||
|
);
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (allowedPrefixConditions.every(condition => !condition(match[1]))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fixes autoformatting in heading nodes: # Heading `bold`
|
||||||
|
// expected result: should not autoformat *bold*; <h1>Heading `bold`</h1>
|
||||||
|
if (state.doc.resolve(start).sameParent(state.doc.resolve(end))) {
|
||||||
|
if (!state.doc.resolve(start).parent.type.allowsMarkType(markType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tr = state.tr;
|
||||||
|
// checks if a selection exists and needs to be removed
|
||||||
|
if (state.selection.from !== state.selection.to) {
|
||||||
|
tr.delete(state.selection.from, state.selection.to);
|
||||||
|
end -= state.selection.to - state.selection.from;
|
||||||
|
}
|
||||||
|
|
||||||
|
const regexStart = end - match[2].length + 1;
|
||||||
|
const codeMark = state.schema.marks.code.create();
|
||||||
|
return applyMarkOnRange(regexStart, end, false, codeMark, tr)
|
||||||
|
.setStoredMarks([codeMark])
|
||||||
|
.delete(regexStart, regexStart + specialChar.length)
|
||||||
|
.removeStoredMark(markType);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const strongRegex1 = /(\S*)(\_\_([^\_\s](\_(?!\_)|[^\_])*[^\_\s]|[^\_\s])\_\_)$/;
|
||||||
|
export const strongRegex2 = /(\S*)(\*\*([^\*\s](\*(?!\*)|[^\*])*[^\*\s]|[^\*\s])\*\*)$/;
|
||||||
|
export const italicRegex1 = /(\S*[^\s\_]*)(\_([^\s\_][^\_]*[^\s\_]|[^\s\_])\_)$/;
|
||||||
|
export const italicRegex2 = /(\S*[^\s\*]*)(\*([^\s\*][^\*]*[^\s\*]|[^\s\*])\*)$/;
|
||||||
|
export const strikeRegex = /(\S*)(\~\~([^\s\~](\~(?!\~)|[^\~])*[^\s\~]|[^\s\~])\~\~)$/;
|
||||||
|
export const codeRegex = /(\S*)(`[^\s][^`]*`)$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create input rules for strong mark
|
||||||
|
*
|
||||||
|
* @param {Schema} schema
|
||||||
|
* @returns {InputRule[]}
|
||||||
|
*/
|
||||||
|
function getStrongInputRules(schema) {
|
||||||
|
// **string** or __strong__ should bold the text
|
||||||
|
|
||||||
|
const markLength = 2;
|
||||||
|
const doubleUnderscoreRule = createInputRule(
|
||||||
|
strongRegex1,
|
||||||
|
addMark(schema.marks.strong, schema, markLength, '__')
|
||||||
|
);
|
||||||
|
|
||||||
|
const doubleAsterixRule = createInputRule(
|
||||||
|
strongRegex2,
|
||||||
|
addMark(schema.marks.strong, schema, markLength, '**')
|
||||||
|
);
|
||||||
|
|
||||||
|
return [doubleUnderscoreRule, doubleAsterixRule];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create input rules for em mark
|
||||||
|
*
|
||||||
|
* @param {Schema} schema
|
||||||
|
* @returns {InputRule[]}
|
||||||
|
*/
|
||||||
|
function getItalicInputRules(schema) {
|
||||||
|
// *string* or _string_ should italic the text
|
||||||
|
const markLength = 1;
|
||||||
|
|
||||||
|
const underscoreRule = createInputRule(
|
||||||
|
italicRegex1,
|
||||||
|
addMark(schema.marks.em, schema, markLength, '_')
|
||||||
|
);
|
||||||
|
|
||||||
|
const asterixRule = createInputRule(
|
||||||
|
italicRegex2,
|
||||||
|
addMark(schema.marks.em, schema, markLength, '*')
|
||||||
|
);
|
||||||
|
|
||||||
|
return [underscoreRule, asterixRule];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create input rules for strike mark
|
||||||
|
*
|
||||||
|
* @param {Schema} schema
|
||||||
|
* @returns {InputRule[]}
|
||||||
|
*/
|
||||||
|
function getStrikeInputRules(schema) {
|
||||||
|
const markLength = 2;
|
||||||
|
const doubleTildeRule = createInputRule(
|
||||||
|
strikeRegex,
|
||||||
|
addMark(schema.marks.strike, schema, markLength, '~~')
|
||||||
|
);
|
||||||
|
|
||||||
|
return [doubleTildeRule];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create input rules for code mark
|
||||||
|
*
|
||||||
|
* @param {Schema} schema
|
||||||
|
* @returns {InputRule[]}
|
||||||
|
*/
|
||||||
|
function getCodeInputRules(schema) {
|
||||||
|
const backTickRule = createInputRule(
|
||||||
|
codeRegex,
|
||||||
|
addCodeMark(schema.marks.code, '`')
|
||||||
|
);
|
||||||
|
|
||||||
|
return [backTickRule];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function textFormattingInputRules(schema) {
|
||||||
|
const rules = [];
|
||||||
|
|
||||||
|
if (schema.marks.strong) {
|
||||||
|
rules.push(...getStrongInputRules(schema));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.marks.em) {
|
||||||
|
rules.push(...getItalicInputRules(schema));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.marks.strike) {
|
||||||
|
rules.push(...getStrikeInputRules(schema));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.marks.code) {
|
||||||
|
rules.push(...getCodeInputRules(schema));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rules.length !== 0) {
|
||||||
|
return inputRules({ rules });
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default textFormattingInputRules;
|
|
@ -0,0 +1,227 @@
|
||||||
|
/* eslint-disable no-cond-assign */
|
||||||
|
/* eslint-disable no-plusplus */
|
||||||
|
import { MenuItem } from 'prosemirror-menu';
|
||||||
|
import { toggleMark, setBlockType } from 'prosemirror-commands';
|
||||||
|
import { undo, redo } from 'prosemirror-history';
|
||||||
|
import { wrapInList } from 'prosemirror-schema-list';
|
||||||
|
import { openPrompt } from '@chatwoot/prosemirror-schema/src/prompt';
|
||||||
|
import { TextField } from '@chatwoot/prosemirror-schema/src/TextField';
|
||||||
|
import {
|
||||||
|
BoldIcon,
|
||||||
|
ItalicsIcon,
|
||||||
|
CodeIcon,
|
||||||
|
UndoIcon,
|
||||||
|
RedoIcon,
|
||||||
|
LinkIcon,
|
||||||
|
Heading3Icon,
|
||||||
|
Heading2Icon,
|
||||||
|
Heading1Icon,
|
||||||
|
TextNumberListIcon,
|
||||||
|
BulletListIcon,
|
||||||
|
} from './icons';
|
||||||
|
import { markActive } from './utils';
|
||||||
|
|
||||||
|
// Helpers to create specific types of items
|
||||||
|
|
||||||
|
function cmdItem(cmd, options) {
|
||||||
|
let passedOptions = {
|
||||||
|
label: options.title,
|
||||||
|
run: cmd,
|
||||||
|
};
|
||||||
|
Object.keys(options).reduce((acc, optionKey) => {
|
||||||
|
acc[optionKey] = options[optionKey];
|
||||||
|
return acc;
|
||||||
|
}, passedOptions);
|
||||||
|
if ((!options.enable || options.enable === true) && !options.select)
|
||||||
|
passedOptions[options.enable ? 'enable' : 'select'] = state => cmd(state);
|
||||||
|
|
||||||
|
return new MenuItem(passedOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function blockTypeIsActive(state, type, attrs) {
|
||||||
|
const { $from } = state.selection;
|
||||||
|
|
||||||
|
let wrapperDepth;
|
||||||
|
let currentDepth = $from.depth;
|
||||||
|
while (currentDepth > 0) {
|
||||||
|
const currentNodeAtDepth = $from.node(currentDepth);
|
||||||
|
|
||||||
|
const comparisonAttrs = {
|
||||||
|
...attrs,
|
||||||
|
};
|
||||||
|
// debugger;
|
||||||
|
if (currentNodeAtDepth.attrs.level) {
|
||||||
|
comparisonAttrs.level = currentNodeAtDepth.attrs.level;
|
||||||
|
}
|
||||||
|
const isType = type.name === currentNodeAtDepth.type.name;
|
||||||
|
const hasAttrs = Object.keys(attrs).reduce((prev, curr) => {
|
||||||
|
if (attrs[curr] !== currentNodeAtDepth.attrs[curr]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
if (isType && hasAttrs) {
|
||||||
|
wrapperDepth = currentDepth;
|
||||||
|
}
|
||||||
|
currentDepth -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return wrapperDepth !== undefined;
|
||||||
|
return wrapperDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleBlockType = (type, attrs) => (state, dispatch) => {
|
||||||
|
const isActive = blockTypeIsActive(state, type, attrs);
|
||||||
|
const newNodeType = isActive ? state.schema.nodes.paragraph : type;
|
||||||
|
const setBlockFunction = setBlockType(newNodeType, attrs);
|
||||||
|
return setBlockFunction(state, dispatch);
|
||||||
|
};
|
||||||
|
|
||||||
|
function markItem(markType, options) {
|
||||||
|
let passedOptions = {
|
||||||
|
active(state) {
|
||||||
|
return markActive(state, markType);
|
||||||
|
},
|
||||||
|
enable: true,
|
||||||
|
};
|
||||||
|
Object.keys(options).reduce((acc, optionKey) => {
|
||||||
|
acc[optionKey] = options[optionKey];
|
||||||
|
return acc;
|
||||||
|
}, passedOptions);
|
||||||
|
return cmdItem(toggleMark(markType), passedOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkItem(markType) {
|
||||||
|
return new MenuItem({
|
||||||
|
title: 'Add or remove link',
|
||||||
|
icon: LinkIcon,
|
||||||
|
active(state) {
|
||||||
|
return markActive(state, markType);
|
||||||
|
},
|
||||||
|
enable(state) {
|
||||||
|
return !state.selection.empty;
|
||||||
|
},
|
||||||
|
run(state, dispatch, view) {
|
||||||
|
if (markActive(state, markType)) {
|
||||||
|
toggleMark(markType)(state, dispatch);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
openPrompt({
|
||||||
|
title: 'Create a link',
|
||||||
|
fields: {
|
||||||
|
href: new TextField({
|
||||||
|
label: 'https://example.com',
|
||||||
|
class: 'small',
|
||||||
|
required: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
callback(attrs) {
|
||||||
|
toggleMark(markType, attrs)(view.state, view.dispatch);
|
||||||
|
view.focus();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function headerItem(nodeType, options) {
|
||||||
|
const { level = 1 } = options;
|
||||||
|
return new MenuItem({
|
||||||
|
title: `Heading ${level}`,
|
||||||
|
icon: options.icon,
|
||||||
|
active(state) {
|
||||||
|
return blockTypeIsActive(state, nodeType, { level });
|
||||||
|
},
|
||||||
|
enable() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
run(state, dispatch, view) {
|
||||||
|
if (blockTypeIsActive(state, nodeType, { level })) {
|
||||||
|
toggleBlockType(nodeType, { level })(state, dispatch);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleBlockType(nodeType, { level })(view.state, view.dispatch);
|
||||||
|
view.focus();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapListItem(nodeType, options) {
|
||||||
|
return cmdItem(wrapInList(nodeType, options.attrs), options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildMenuItems(schema) {
|
||||||
|
let r = {
|
||||||
|
toggleStrong: markItem(schema.marks.strong, {
|
||||||
|
title: 'Toggle strong style',
|
||||||
|
icon: BoldIcon,
|
||||||
|
}),
|
||||||
|
toggleEm: markItem(schema.marks.em, {
|
||||||
|
title: 'Toggle emphasis',
|
||||||
|
icon: ItalicsIcon,
|
||||||
|
}),
|
||||||
|
toggleCode: markItem(schema.marks.code, {
|
||||||
|
title: 'Toggle code font',
|
||||||
|
icon: CodeIcon,
|
||||||
|
}),
|
||||||
|
toggleLink: linkItem(schema.marks.link),
|
||||||
|
wrapBulletList: wrapListItem(schema.nodes.bullet_list, {
|
||||||
|
title: 'Wrap in bullet list',
|
||||||
|
icon: BulletListIcon,
|
||||||
|
}),
|
||||||
|
wrapOrderedList: wrapListItem(schema.nodes.ordered_list, {
|
||||||
|
title: 'Wrap in ordered list',
|
||||||
|
icon: TextNumberListIcon,
|
||||||
|
}),
|
||||||
|
toggleH1: headerItem(schema.nodes.heading, {
|
||||||
|
level: 1,
|
||||||
|
title: 'Toggle code font',
|
||||||
|
icon: Heading1Icon,
|
||||||
|
}),
|
||||||
|
toggleH2: headerItem(schema.nodes.heading, {
|
||||||
|
level: 2,
|
||||||
|
title: 'Toggle code font',
|
||||||
|
icon: Heading2Icon,
|
||||||
|
}),
|
||||||
|
toggleH3: headerItem(schema.nodes.heading, {
|
||||||
|
level: 3,
|
||||||
|
title: 'Toggle code font',
|
||||||
|
icon: Heading3Icon,
|
||||||
|
}),
|
||||||
|
undoItem: new MenuItem({
|
||||||
|
title: 'Undo last change',
|
||||||
|
run: undo,
|
||||||
|
enable: state => undo(state),
|
||||||
|
icon: UndoIcon,
|
||||||
|
}),
|
||||||
|
redoItem: new MenuItem({
|
||||||
|
title: 'Redo last undone change',
|
||||||
|
run: redo,
|
||||||
|
enable: state => redo(state),
|
||||||
|
icon: RedoIcon,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let cut = arr => arr.filter(x => x);
|
||||||
|
|
||||||
|
r.inlineMenu = [
|
||||||
|
cut([r.toggleStrong, r.toggleEm, r.toggleCode, r.toggleLink]),
|
||||||
|
];
|
||||||
|
r.blockMenu = [
|
||||||
|
cut([
|
||||||
|
r.toggleH1,
|
||||||
|
r.toggleH2,
|
||||||
|
r.toggleH3,
|
||||||
|
r.wrapBulletList,
|
||||||
|
r.wrapOrderedList,
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
r.fullMenu = r.inlineMenu.concat([[r.undoItem, r.redoItem]], r.blockMenu);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
|
@ -0,0 +1,225 @@
|
||||||
|
import { InputRule } from 'prosemirror-inputrules';
|
||||||
|
import { Fragment, Slice } from 'prosemirror-model';
|
||||||
|
import { TextSelection } from 'prosemirror-state';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a mark (with specific attribute values) exists anywhere in the selection.
|
||||||
|
*/
|
||||||
|
export const markActive = (state, mark) => {
|
||||||
|
let { from, $from, to, empty } = state.selection;
|
||||||
|
if (empty) return mark.isInSet(state.storedMarks || $from.marks());
|
||||||
|
return state.doc.rangeHasMark(from, to, mark);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hasCode = (state, pos) => {
|
||||||
|
const { code } = state.schema.marks;
|
||||||
|
const node = pos >= 0 && state.doc.nodeAt(pos);
|
||||||
|
if (node) {
|
||||||
|
return !!node.marks.filter(mark => mark.type === code).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasUnsupportedMarkForBlockInputRule = (state, start, end) => {
|
||||||
|
const {
|
||||||
|
doc,
|
||||||
|
schema: { marks },
|
||||||
|
} = state;
|
||||||
|
let unsupportedMarksPresent = false;
|
||||||
|
const isUnsupportedMark = node =>
|
||||||
|
node.type === marks.code || node.type === marks.link;
|
||||||
|
doc.nodesBetween(start, end, node => {
|
||||||
|
unsupportedMarksPresent =
|
||||||
|
unsupportedMarksPresent ||
|
||||||
|
node.marks.filter(isUnsupportedMark).length > 0;
|
||||||
|
});
|
||||||
|
return unsupportedMarksPresent;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasUnsupportedMarkForInputRule = (state, start, end) => {
|
||||||
|
const {
|
||||||
|
doc,
|
||||||
|
schema: { marks },
|
||||||
|
} = state;
|
||||||
|
let unsupportedMarksPresent = false;
|
||||||
|
const isCodemark = mark => mark.type === marks.code;
|
||||||
|
doc.nodesBetween(start, end, node => {
|
||||||
|
unsupportedMarksPresent =
|
||||||
|
unsupportedMarksPresent || node.marks.filter(isCodemark).length > 0;
|
||||||
|
});
|
||||||
|
return unsupportedMarksPresent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function defaultInputRuleHandler(inputRule, isBlockNodeRule = false) {
|
||||||
|
const originalHandler = inputRule.handler;
|
||||||
|
inputRule.handler = (state, match, start, end) => {
|
||||||
|
const unsupportedMarks = isBlockNodeRule
|
||||||
|
? hasUnsupportedMarkForBlockInputRule(state, start, end)
|
||||||
|
: hasUnsupportedMarkForInputRule(state, start, end);
|
||||||
|
if (state.selection.$from.parent.type.spec.code || unsupportedMarks) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return originalHandler(state, match, start, end);
|
||||||
|
};
|
||||||
|
return inputRule;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createInputRule = (match, handler, isBlockNodeRule = false) =>
|
||||||
|
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.
|
||||||
|
export const leafNodeReplacementCharacter = '\ufffc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if node contains only empty inline nodes and hardBreaks.
|
||||||
|
*/
|
||||||
|
export function hasVisibleContent(node) {
|
||||||
|
const isInlineNodeHasVisibleContent = inlineNode => {
|
||||||
|
return inlineNode.isText
|
||||||
|
? !!inlineNode.textContent.trim()
|
||||||
|
: inlineNode.type.name !== 'hardBreak';
|
||||||
|
};
|
||||||
|
|
||||||
|
if (node.isInline) {
|
||||||
|
return isInlineNodeHasVisibleContent(node);
|
||||||
|
}
|
||||||
|
if (node.isBlock && (node.isLeaf || node.isAtom)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!node.childCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < node.childCount; index += 1) {
|
||||||
|
const child = node.child(index);
|
||||||
|
|
||||||
|
if (hasVisibleContent(child)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if node is an empty paragraph.
|
||||||
|
*/
|
||||||
|
export function isEmptyParagraph(node) {
|
||||||
|
return (
|
||||||
|
!node ||
|
||||||
|
(node.type.name === 'paragraph' && !node.textContent && !node.childCount)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a node has any content. Ignores node that only contain empty block nodes.
|
||||||
|
*/
|
||||||
|
export function isNodeEmpty(node) {
|
||||||
|
if (node && node.textContent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!node ||
|
||||||
|
!node.childCount ||
|
||||||
|
(node.childCount === 1 && isEmptyParagraph(node.firstChild))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const block = [];
|
||||||
|
const nonBlock = [];
|
||||||
|
|
||||||
|
node.forEach(child =>
|
||||||
|
child.isInline ? nonBlock.push(child) : block.push(child)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
!nonBlock.length &&
|
||||||
|
!block.filter(
|
||||||
|
childNode =>
|
||||||
|
(!!childNode.childCount &&
|
||||||
|
!(
|
||||||
|
childNode.childCount === 1 && isEmptyParagraph(childNode.firstChild)
|
||||||
|
)) ||
|
||||||
|
childNode.isAtom
|
||||||
|
).length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const compose = (...functions) => args =>
|
||||||
|
functions.reduceRight((arg, fn) => fn(arg), args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper to get the underlying array of a fragment.
|
||||||
|
*/
|
||||||
|
export function getFragmentBackingArray(fragment) {
|
||||||
|
return fragment.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapFragment(content, callback, parent) {
|
||||||
|
const children = [];
|
||||||
|
for (let i = 0, size = content.childCount; i < size; i += 1) {
|
||||||
|
const node = content.child(i);
|
||||||
|
const transformed = node.isLeaf
|
||||||
|
? callback(node, parent, i)
|
||||||
|
: callback(
|
||||||
|
node.copy(mapFragment(node.content, callback, node)),
|
||||||
|
parent,
|
||||||
|
i
|
||||||
|
);
|
||||||
|
if (transformed) {
|
||||||
|
if (transformed) {
|
||||||
|
children.push(...getFragmentBackingArray(transformed));
|
||||||
|
} else if (Array.isArray(transformed)) {
|
||||||
|
children.push(...transformed);
|
||||||
|
} else {
|
||||||
|
children.push(transformed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Fragment.fromArray(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapSlice(slice, callback) {
|
||||||
|
const fragment = mapFragment(slice.content, callback);
|
||||||
|
return new Slice(fragment, slice.openStart, slice.openEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function atTheEndOfDoc(state) {
|
||||||
|
const { selection, doc } = state;
|
||||||
|
return doc.nodeSize - selection.$to.pos - 2 === selection.$to.depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canMoveDown(state) {
|
||||||
|
const { selection } = state;
|
||||||
|
|
||||||
|
if (selection instanceof TextSelection) {
|
||||||
|
if (!selection.empty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !atTheEndOfDoc(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function atTheBeginningOfDoc(state) {
|
||||||
|
const { selection } = state;
|
||||||
|
return selection.$from.pos === selection.$from.depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canMoveUp(state) {
|
||||||
|
const { selection } = state;
|
||||||
|
|
||||||
|
if (selection instanceof TextSelection) {
|
||||||
|
if (!selection.empty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !atTheBeginningOfDoc(state);
|
||||||
|
}
|
|
@ -14,8 +14,6 @@
|
||||||
v-model="articleContent"
|
v-model="articleContent"
|
||||||
class="article-content"
|
class="article-content"
|
||||||
:placeholder="$t('HELP_CENTER.EDIT_ARTICLE.CONTENT_PLACEHOLDER')"
|
:placeholder="$t('HELP_CENTER.EDIT_ARTICLE.CONTENT_PLACEHOLDER')"
|
||||||
:is-format-mode="true"
|
|
||||||
:override-line-breaks="true"
|
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
@input="onContentInput"
|
@input="onContentInput"
|
||||||
|
@ -26,7 +24,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { debounce } from '@chatwoot/utils';
|
import { debounce } from '@chatwoot/utils';
|
||||||
import ResizableTextArea from 'shared/components/ResizableTextArea';
|
import ResizableTextArea from 'shared/components/ResizableTextArea';
|
||||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
|
||||||
|
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/FullEditor.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -48,7 +48,10 @@
|
||||||
"ninja-keys": "^1.1.9",
|
"ninja-keys": "^1.1.9",
|
||||||
"opus-recorder": "^8.0.5",
|
"opus-recorder": "^8.0.5",
|
||||||
"prosemirror-markdown": "1.5.1",
|
"prosemirror-markdown": "1.5.1",
|
||||||
|
"prosemirror-menu": "^1.2.1",
|
||||||
"prosemirror-state": "1.3.4",
|
"prosemirror-state": "1.3.4",
|
||||||
|
"prosemirror-tables": "^1.3.0",
|
||||||
|
"prosemirror-utils": "^0.9.6",
|
||||||
"prosemirror-view": "1.18.4",
|
"prosemirror-view": "1.18.4",
|
||||||
"semver": "7.3.5",
|
"semver": "7.3.5",
|
||||||
"spinkit": "~1.2.5",
|
"spinkit": "~1.2.5",
|
||||||
|
|
71
yarn.lock
71
yarn.lock
|
@ -12228,6 +12228,11 @@ orderedmap@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.1.1.tgz#c618e77611b3b21d0fe3edc92586265e0059c789"
|
resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.1.1.tgz#c618e77611b3b21d0fe3edc92586265e0059c789"
|
||||||
integrity sha512-3Ux8um0zXbVacKUkcytc0u3HgC0b0bBLT+I60r2J/En72cI0nZffqrA7Xtf2Hqs27j1g82llR5Mhbd0Z1XW4AQ==
|
integrity sha512-3Ux8um0zXbVacKUkcytc0u3HgC0b0bBLT+I60r2J/En72cI0nZffqrA7Xtf2Hqs27j1g82llR5Mhbd0Z1XW4AQ==
|
||||||
|
|
||||||
|
orderedmap@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.0.tgz#819457082fa3a06abd316d83a281a1ca467437cd"
|
||||||
|
integrity sha512-/pIFexOm6S70EPdznemIz3BQZoJ4VTFrhqzu0ACBqBgeLsLxq8e6Jim63ImIfwW/zAD1AlXpRMlOv3aghmo4dA==
|
||||||
|
|
||||||
original@^1.0.0:
|
original@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
|
resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
|
||||||
|
@ -13645,6 +13650,14 @@ prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.4:
|
||||||
prosemirror-state "^1.0.0"
|
prosemirror-state "^1.0.0"
|
||||||
w3c-keyname "^2.2.0"
|
w3c-keyname "^2.2.0"
|
||||||
|
|
||||||
|
prosemirror-keymap@^1.1.2:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.0.tgz#d5cc9da9b712020690a994b50b92a0e448a60bf5"
|
||||||
|
integrity sha512-TdSfu+YyLDd54ufN/ZeD1VtBRYpgZnTPnnbY+4R08DDgs84KrIPEPbJL8t1Lm2dkljFx6xeBE26YWH3aIzkPKg==
|
||||||
|
dependencies:
|
||||||
|
prosemirror-state "^1.0.0"
|
||||||
|
w3c-keyname "^2.2.0"
|
||||||
|
|
||||||
prosemirror-markdown@1.5.1:
|
prosemirror-markdown@1.5.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.5.1.tgz#877c7faea2225d3c52e988599bbe4457bcb3190f"
|
resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.5.1.tgz#877c7faea2225d3c52e988599bbe4457bcb3190f"
|
||||||
|
@ -13663,6 +13676,16 @@ prosemirror-menu@^1.1.4:
|
||||||
prosemirror-history "^1.0.0"
|
prosemirror-history "^1.0.0"
|
||||||
prosemirror-state "^1.0.0"
|
prosemirror-state "^1.0.0"
|
||||||
|
|
||||||
|
prosemirror-menu@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/prosemirror-menu/-/prosemirror-menu-1.2.1.tgz#94d99a8547b7ba5680c20e9c497ce19846ce3b2c"
|
||||||
|
integrity sha512-sBirXxVfHalZO4f1ZS63WzewINK4182+7dOmoMeBkqYO8wqMBvBS7wQuwVOHnkMWPEh0+N0LJ856KYUN+vFkmQ==
|
||||||
|
dependencies:
|
||||||
|
crelt "^1.0.0"
|
||||||
|
prosemirror-commands "^1.0.0"
|
||||||
|
prosemirror-history "^1.0.0"
|
||||||
|
prosemirror-state "^1.0.0"
|
||||||
|
|
||||||
prosemirror-model@^1.0.0, prosemirror-model@^1.1.0:
|
prosemirror-model@^1.0.0, prosemirror-model@^1.1.0:
|
||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.14.1.tgz#d784c67f95a5d66b853e82ff9a87a50353ef9cd5"
|
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.14.1.tgz#d784c67f95a5d66b853e82ff9a87a50353ef9cd5"
|
||||||
|
@ -13670,6 +13693,13 @@ prosemirror-model@^1.0.0, prosemirror-model@^1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
orderedmap "^1.1.0"
|
orderedmap "^1.1.0"
|
||||||
|
|
||||||
|
prosemirror-model@^1.16.0, prosemirror-model@^1.8.1:
|
||||||
|
version "1.18.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.18.3.tgz#d1026a78cff928fd600e90d87cf7d162e0a4e3fd"
|
||||||
|
integrity sha512-yUVejauEY3F1r7PDy4UJKEGeIU+KFc71JQl5sNvG66CLVdKXRjhWpBW6KMeduGsmGOsw85f6EGrs6QxIKOVILA==
|
||||||
|
dependencies:
|
||||||
|
orderedmap "^2.0.0"
|
||||||
|
|
||||||
prosemirror-schema-list@^1.1.4:
|
prosemirror-schema-list@^1.1.4:
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.1.4.tgz#471f9caf2d2bed93641d2e490434c0d2d4330df1"
|
resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.1.4.tgz#471f9caf2d2bed93641d2e490434c0d2d4330df1"
|
||||||
|
@ -13686,6 +13716,26 @@ prosemirror-state@1.3.4, prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, pro
|
||||||
prosemirror-model "^1.0.0"
|
prosemirror-model "^1.0.0"
|
||||||
prosemirror-transform "^1.0.0"
|
prosemirror-transform "^1.0.0"
|
||||||
|
|
||||||
|
prosemirror-state@^1.3.1:
|
||||||
|
version "1.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.2.tgz#f93bd8a33a4454efab917ba9b738259d828db7e5"
|
||||||
|
integrity sha512-puuzLD2mz/oTdfgd8msFbe0A42j5eNudKAAPDB0+QJRw8cO1ygjLmhLrg9RvDpf87Dkd6D4t93qdef00KKNacQ==
|
||||||
|
dependencies:
|
||||||
|
prosemirror-model "^1.0.0"
|
||||||
|
prosemirror-transform "^1.0.0"
|
||||||
|
prosemirror-view "^1.27.0"
|
||||||
|
|
||||||
|
prosemirror-tables@^1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.3.0.tgz#262fa7d030f7bebef7b5fd9a045bce9a786c198c"
|
||||||
|
integrity sha512-ujzOb37O2ahmqI626Y0N0V/SZxuA9OGNYnsIMWdfecwkc8S8OShOqeD4kKUxpD0JcP81Z8qy/ulrXQuKhS4WUg==
|
||||||
|
dependencies:
|
||||||
|
prosemirror-keymap "^1.1.2"
|
||||||
|
prosemirror-model "^1.8.1"
|
||||||
|
prosemirror-state "^1.3.1"
|
||||||
|
prosemirror-transform "^1.2.1"
|
||||||
|
prosemirror-view "^1.13.3"
|
||||||
|
|
||||||
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0:
|
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.3.2.tgz#5620ebe7379e6fae4f34ecc881886cb22ce96579"
|
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.3.2.tgz#5620ebe7379e6fae4f34ecc881886cb22ce96579"
|
||||||
|
@ -13693,6 +13743,18 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prosemirror-model "^1.0.0"
|
prosemirror-model "^1.0.0"
|
||||||
|
|
||||||
|
prosemirror-transform@^1.2.1:
|
||||||
|
version "1.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.7.0.tgz#a8a0768f3ee6418d26ebef435beda9d43c65e472"
|
||||||
|
integrity sha512-O4T697Cqilw06Zvc3Wm+e237R6eZtJL/xGMliCi+Uo8VL6qHk6afz1qq0zNjT3eZMuYwnP8ZS0+YxX/tfcE9TQ==
|
||||||
|
dependencies:
|
||||||
|
prosemirror-model "^1.0.0"
|
||||||
|
|
||||||
|
prosemirror-utils@^0.9.6:
|
||||||
|
version "0.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.9.6.tgz#3d97bd85897e3b535555867dc95a51399116a973"
|
||||||
|
integrity sha512-UC+j9hQQ1POYfMc5p7UFxBTptRiGPR7Kkmbl3jVvU8VgQbkI89tR/GK+3QYC8n+VvBZrtAoCrJItNhWSxX3slA==
|
||||||
|
|
||||||
prosemirror-view@1.18.4, prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.17.2:
|
prosemirror-view@1.18.4, prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.17.2:
|
||||||
version "1.18.4"
|
version "1.18.4"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.18.4.tgz#179141df117cf414434ade08115f2e233d135f6d"
|
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.18.4.tgz#179141df117cf414434ade08115f2e233d135f6d"
|
||||||
|
@ -13702,6 +13764,15 @@ prosemirror-view@1.18.4, prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prose
|
||||||
prosemirror-state "^1.0.0"
|
prosemirror-state "^1.0.0"
|
||||||
prosemirror-transform "^1.1.0"
|
prosemirror-transform "^1.1.0"
|
||||||
|
|
||||||
|
prosemirror-view@^1.13.3, prosemirror-view@^1.27.0:
|
||||||
|
version "1.29.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.29.1.tgz#9a4938d1a863ca76e23c6573d30e3ece2b17d9a0"
|
||||||
|
integrity sha512-OhujVZSDsh0l0PyHNdfaBj6DBkbhYaCfbaxmTeFrMKd/eWS+G6IC+OAbmR9IsLC8Se1HSbphMaXnsXjupHL3UQ==
|
||||||
|
dependencies:
|
||||||
|
prosemirror-model "^1.16.0"
|
||||||
|
prosemirror-state "^1.0.0"
|
||||||
|
prosemirror-transform "^1.1.0"
|
||||||
|
|
||||||
proto-list@~1.2.1:
|
proto-list@~1.2.1:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
||||||
|
|
Loading…
Reference in a new issue