mirror of
https://github.com/discourse/discourse.git
synced 2025-02-07 03:48:23 +00:00
DEV: autocomplete/emoji improvements
This commit is contained in:
parent
9b4f3d03f6
commit
dc7d5a8cf8
@ -581,7 +581,14 @@ export default function (options) {
|
||||
let term = options.textHandler
|
||||
.getValue()
|
||||
.substring(completeStart + (options.key ? 1 : 0), cp);
|
||||
updateAutoComplete(dataSource(term, options));
|
||||
if (
|
||||
!options.key ||
|
||||
options.textHandler.getValue()[completeStart] === options.key
|
||||
) {
|
||||
updateAutoComplete(dataSource(term, options));
|
||||
} else {
|
||||
closeAutocomplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
||||
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
|
||||
import { next } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
import "../extensions/register-additional"; // registers all non-core extensions
|
||||
import "../extensions/register-default";
|
||||
import { baseKeymap } from "prosemirror-commands";
|
||||
import * as ProsemirrorCommands from "prosemirror-commands";
|
||||
import { dropCursor } from "prosemirror-dropcursor";
|
||||
@ -32,6 +32,8 @@ import placeholder from "../extensions/placeholder";
|
||||
import * as utils from "../lib/plugin-utils";
|
||||
import TextManipulation from "../lib/text-manipulation";
|
||||
|
||||
const AUTOCOMPLETE_KEY_DOWN_SUPPRESS = ["Enter", "Tab"];
|
||||
|
||||
/**
|
||||
* @typedef PluginContext
|
||||
* @property {string} placeholder
|
||||
@ -167,6 +169,7 @@ export default class ProsemirrorEditor extends Component {
|
||||
|
||||
this.view = new EditorView(container, {
|
||||
convertFromMarkdown: this.convertFromMarkdown,
|
||||
convertToMarkdown: this.serializer.convert.bind(this.serializer),
|
||||
getContext: params.getContext,
|
||||
nodeViews: extractNodeViews(this.extensions),
|
||||
state,
|
||||
@ -176,7 +179,7 @@ export default class ProsemirrorEditor extends Component {
|
||||
this.view.updateState(this.view.state.apply(tr));
|
||||
|
||||
if (tr.docChanged && tr.getMeta("addToHistory") !== false) {
|
||||
// TODO(renato): avoid calling this on every change
|
||||
// If this gets expensive, we can debounce it
|
||||
const value = this.serializer.convert(this.view.state.doc);
|
||||
this.#lastSerialized = value;
|
||||
this.args.change?.({ target: { value } });
|
||||
@ -193,9 +196,10 @@ export default class ProsemirrorEditor extends Component {
|
||||
},
|
||||
},
|
||||
handleKeyDown: (view, event) => {
|
||||
// skip the event if it's an Enter keypress and the autocomplete is open
|
||||
// suppress if Enter/Tab and the autocomplete is open
|
||||
return (
|
||||
event.key === "Enter" && !!document.querySelector(".autocomplete")
|
||||
AUTOCOMPLETE_KEY_DOWN_SUPPRESS.includes(event.key) &&
|
||||
!!document.querySelector(".autocomplete")
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -38,7 +38,6 @@ const extension = {
|
||||
},
|
||||
];
|
||||
},
|
||||
leafText: (node) => `:${node.attrs.code}:`,
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -27,7 +27,6 @@ const extension = {
|
||||
`#${node.attrs.name}`,
|
||||
];
|
||||
},
|
||||
leafText: (node) => `#${node.attrs.name}`,
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -28,7 +28,6 @@ const extension = {
|
||||
`@${node.attrs.name}`,
|
||||
];
|
||||
},
|
||||
leafText: (node) => `@${node.attrs.name}`,
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -32,7 +32,7 @@ export default class ProsemirrorTextManipulation {
|
||||
});
|
||||
}
|
||||
|
||||
getSelected(trimLeading, opts) {
|
||||
getSelected() {
|
||||
const start = this.view.state.selection.from;
|
||||
const end = this.view.state.selection.to;
|
||||
const value = this.view.state.doc.textBetween(start, end, " ", " ");
|
||||
@ -70,7 +70,7 @@ export default class ProsemirrorTextManipulation {
|
||||
this.applySurround(this.getSelected(), head, tail, exampleKey, opts);
|
||||
}
|
||||
|
||||
applySurround(sel, head, tail, exampleKey, opts) {
|
||||
applySurround(sel, head, tail, exampleKey) {
|
||||
const applySurroundMap = {
|
||||
italic_text: this.schema.marks.em,
|
||||
bold_text: this.schema.marks.strong,
|
||||
@ -94,7 +94,7 @@ export default class ProsemirrorTextManipulation {
|
||||
);
|
||||
}
|
||||
|
||||
addText(sel, text, options) {
|
||||
addText(sel, text) {
|
||||
const doc = this.view.props.convertFromMarkdown(text);
|
||||
|
||||
// assumes it returns a single block node
|
||||
@ -123,12 +123,7 @@ export default class ProsemirrorTextManipulation {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
applyList(_selection, head, exampleKey, opts) {
|
||||
// This is similar to applySurround, but doing it line by line
|
||||
// We may use markdown parsing as a fallback if we don't identify the exampleKey
|
||||
// similarly to applySurround
|
||||
// TODO to check actual applyList uses in the wild
|
||||
|
||||
applyList(_selection, head, exampleKey) {
|
||||
let command;
|
||||
|
||||
const isInside = (type) => {
|
||||
@ -143,27 +138,18 @@ export default class ProsemirrorTextManipulation {
|
||||
};
|
||||
|
||||
if (exampleKey === "list_item") {
|
||||
if (head === "* ") {
|
||||
command = isInside(this.schema.nodes.bullet_list)
|
||||
? lift
|
||||
: wrapIn(this.schema.nodes.bullet_list);
|
||||
} else {
|
||||
command = isInside(this.schema.nodes.ordered_list)
|
||||
? lift
|
||||
: wrapIn(this.schema.nodes.ordered_list);
|
||||
}
|
||||
} else {
|
||||
const applyListMap = {
|
||||
blockquote_text: this.schema.nodes.blockquote,
|
||||
};
|
||||
const nodeType =
|
||||
head === "* "
|
||||
? this.schema.nodes.bullet_list
|
||||
: this.schema.nodes.ordered_list;
|
||||
|
||||
if (applyListMap[exampleKey]) {
|
||||
command = isInside(applyListMap[exampleKey])
|
||||
? lift
|
||||
: wrapIn(applyListMap[exampleKey]);
|
||||
} else {
|
||||
// TODO(renato): fallback to markdown parsing
|
||||
}
|
||||
command = isInside(this.schema.nodes.list_item) ? lift : wrapIn(nodeType);
|
||||
} else if (exampleKey === "blockquote_text") {
|
||||
command = isInside(this.schema.nodes.blockquote)
|
||||
? lift
|
||||
: wrapIn(this.schema.nodes.blockquote);
|
||||
} else {
|
||||
throw new Error("Unknown exampleKey");
|
||||
}
|
||||
|
||||
command?.(this.view.state, this.view.dispatch);
|
||||
@ -201,7 +187,7 @@ export default class ProsemirrorTextManipulation {
|
||||
paste() {
|
||||
// Intentionally no-op
|
||||
// Pasting markdown is being handled by the markdown-paste extension
|
||||
// Pasting an url on top of a text is being handled by the link extension
|
||||
// Pasting a url on top of a text is being handled by the link extension
|
||||
}
|
||||
|
||||
selectText(from, length, opts) {
|
||||
@ -224,17 +210,6 @@ export default class ProsemirrorTextManipulation {
|
||||
return this.autocompleteHandler.inCodeBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the textual caret position within the selected text block
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
getCaretPosition() {
|
||||
const { $anchor } = this.view.state.selection;
|
||||
|
||||
return $anchor.pos - $anchor.start();
|
||||
}
|
||||
|
||||
indentSelection(direction) {
|
||||
const { selection } = this.view.state;
|
||||
|
||||
@ -264,11 +239,50 @@ export default class ProsemirrorTextManipulation {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
replaceText(oldValue, newValue, opts) {
|
||||
// this method should be deprecated, this is not very reliable:
|
||||
// we're converting the current document to markdown, replacing it, and setting its result
|
||||
// as the new document content
|
||||
// TODO
|
||||
replaceText(oldValue, newValue, opts = {}) {
|
||||
const markdown = this.view.props.convertToMarkdown(this.view.state.doc);
|
||||
|
||||
const regex = opts.regex || new RegExp(oldValue, "g");
|
||||
const index = opts.index || 0;
|
||||
let matchCount = 0;
|
||||
|
||||
const newMarkdown = markdown.replace(regex, (match) => {
|
||||
if (matchCount++ === index) {
|
||||
return newValue;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
if (markdown === newMarkdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newDoc = this.view.props.convertFromMarkdown(newMarkdown);
|
||||
if (!newDoc) {
|
||||
return;
|
||||
}
|
||||
|
||||
const diff = newValue.length - oldValue.length;
|
||||
const startOffset = this.view.state.selection.from + diff;
|
||||
const endOffset = this.view.state.selection.to + diff;
|
||||
|
||||
const tr = this.view.state.tr.replaceWith(
|
||||
0,
|
||||
this.view.state.doc.content.size,
|
||||
newDoc.content
|
||||
);
|
||||
|
||||
if (
|
||||
!opts.skipNewSelection &&
|
||||
(opts.forceFocus || this.view.dom === document.activeElement)
|
||||
) {
|
||||
const adjustedStart = Math.min(startOffset, tr.doc.content.size);
|
||||
const adjustedEnd = Math.min(endOffset, tr.doc.content.size);
|
||||
|
||||
tr.setSelection(TextSelection.create(tr.doc, adjustedStart, adjustedEnd));
|
||||
}
|
||||
|
||||
this.view.dispatch(tr);
|
||||
}
|
||||
|
||||
toggleDirection() {
|
||||
@ -313,21 +327,6 @@ class ProsemirrorAutocompleteHandler {
|
||||
const from = this.view.state.selection.from - node.nodeSize + start;
|
||||
const to = this.view.state.selection.from - node.nodeSize + end + 1;
|
||||
|
||||
// Alternative approach using inputRules, if `convertFromMarkdown` is too expensive
|
||||
//
|
||||
// let replaced;
|
||||
// for (const plugin of this.view.state.plugins) {
|
||||
// if (plugin.spec.isInputRules) {
|
||||
// replaced ||= plugin.props.handleTextInput(this.view, from, to, term, null);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (!replaced) {
|
||||
// this.view.dispatch(
|
||||
// this.view.state.tr.replaceWith(from, to, this.schema.text(term))
|
||||
// );
|
||||
// }
|
||||
|
||||
const doc = this.view.props.convertFromMarkdown(term);
|
||||
|
||||
const tr = this.view.state.tr.replaceWith(
|
||||
@ -449,7 +448,7 @@ class ProsemirrorPlaceholderHandler {
|
||||
return true;
|
||||
});
|
||||
|
||||
// keeping compatibility with plugins that change the image node via markdown
|
||||
// keeping compatibility with plugins that change the upload markdown
|
||||
const doc = this.view.props.convertFromMarkdown(markdown);
|
||||
|
||||
this.view.dispatch(
|
||||
|
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -329,6 +329,9 @@ importers:
|
||||
morphlex:
|
||||
specifier: ^0.0.16
|
||||
version: 0.0.16
|
||||
orderedmap:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
pretty-text:
|
||||
specifier: workspace:1.0.0
|
||||
version: link:../pretty-text
|
||||
|
Loading…
x
Reference in New Issue
Block a user