1
0
mirror of https://github.com/flarum/core.git synced 2025-07-27 11:40:24 +02:00

Update for Editor Drivers Abstraction (#61)

This commit is contained in:
Alexander Skvortsov
2021-02-26 16:20:07 -05:00
committed by GitHub
parent b06f6470e7
commit d4db946c31
4 changed files with 50 additions and 45 deletions

View File

@@ -3445,7 +3445,7 @@
}, },
"safe-regex": { "safe-regex": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"requires": { "requires": {
"ret": "~0.1.10" "ret": "~0.1.10"
@@ -3839,11 +3839,6 @@
} }
} }
}, },
"textarea-caret": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz",
"integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q=="
},
"through2": { "through2": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",

View File

@@ -3,7 +3,6 @@
"name": "@flarum/mentions", "name": "@flarum/mentions",
"dependencies": { "dependencies": {
"flarum-webpack-config": "0.1.0-beta.10", "flarum-webpack-config": "0.1.0-beta.10",
"textarea-caret": "^3.1.0",
"webpack": "^4.43.0", "webpack": "^4.43.0",
"webpack-cli": "^3.3.11" "webpack-cli": "^3.3.11"
}, },

View File

@@ -1,5 +1,3 @@
import getCaretCoordinates from 'textarea-caret';
import { extend } from 'flarum/extend'; import { extend } from 'flarum/extend';
import TextEditor from 'flarum/components/TextEditor'; import TextEditor from 'flarum/components/TextEditor';
import TextEditorButton from 'flarum/components/TextEditorButton'; import TextEditorButton from 'flarum/components/TextEditorButton';
@@ -14,12 +12,28 @@ import { truncate } from 'flarum/utils/string';
import AutocompleteDropdown from './fragments/AutocompleteDropdown'; import AutocompleteDropdown from './fragments/AutocompleteDropdown';
export default function addComposerAutocomplete() { export default function addComposerAutocomplete() {
extend(TextEditor.prototype, 'oncreate', function () {
const $container = $('<div class="ComposerBody-mentionsDropdownContainer"></div>'); const $container = $('<div class="ComposerBody-mentionsDropdownContainer"></div>');
const dropdown = new AutocompleteDropdown(); const dropdown = new AutocompleteDropdown();
const $textarea = this.$('textarea').wrap('<div class="ComposerBody-mentionsWrapper"></div>');
extend(TextEditor.prototype, 'oncreate', function (params) {
const $editor = this.$('.TextEditor-editor').wrap('<div class="ComposerBody-mentionsWrapper"></div>');
this.navigator = new KeyboardNavigatable();
this.navigator
.when(() => dropdown.active)
.onUp(() => dropdown.navigate(-1))
.onDown(() => dropdown.navigate(1))
.onSelect(dropdown.complete.bind(dropdown))
.onCancel(dropdown.hide.bind(dropdown))
.bindTo($editor);
$editor.after($container);
});
extend(TextEditor.prototype, 'buildEditorParams', function (params) {
const searched = []; const searched = [];
let mentionStart; let relMentionStart;
let absMentionStart;
let typed; let typed;
let searchTimeout; let searchTimeout;
@@ -30,38 +44,27 @@ export default function addComposerAutocomplete() {
const returnedUserIds = new Set(returnedUsers.map(u => u.id())); const returnedUserIds = new Set(returnedUsers.map(u => u.id()));
const applySuggestion = (replacement) => { const applySuggestion = (replacement) => {
app.composer.editor.replaceBeforeCursor(mentionStart - 1, replacement + ' '); app.composer.editor.replaceBeforeCursor(absMentionStart - 1, replacement + ' ');
dropdown.hide(); dropdown.hide();
}; };
this.navigator = new KeyboardNavigatable(); params.inputListeners.push(function(e) {
this.navigator const selection = app.composer.editor.getSelectionRange();
.when(() => dropdown.active)
.onUp(() => dropdown.navigate(-1))
.onDown(() => dropdown.navigate(1))
.onSelect(dropdown.complete.bind(dropdown))
.onCancel(dropdown.hide.bind(dropdown))
.bindTo($textarea);
$textarea const cursor = selection[0];
.after($container)
.on('click keyup input', function(e) {
// Up, down, enter, tab, escape, left, right.
if ([9, 13, 27, 40, 38, 37, 39].indexOf(e.which) !== -1) return;
const cursor = this.selectionStart; if (selection[1] - cursor > 0) return;
if (this.selectionEnd - cursor > 0) return;
// Search backwards from the cursor for an '@' symbol. If we find one, // Search backwards from the cursor for an '@' symbol. If we find one,
// we will want to show the autocomplete dropdown! // we will want to show the autocomplete dropdown!
const value = this.value; const lastChunk = app.composer.editor.getLastNChars(30);
mentionStart = 0; absMentionStart = 0;
for (let i = cursor - 1; i >= cursor - 30; i--) { for (let i = lastChunk.length - 1; i >= 0; i--) {
const character = value.substr(i, 1); const character = lastChunk.substr(i, 1);
if (character === '@') { if (character === '@' && (i == 0 || /\s/.test(lastChunk.substr(i - 1, 1)))) {
mentionStart = i + 1; relMentionStart = i + 1;
absMentionStart = cursor - lastChunk.length + i + 1;
break; break;
} }
} }
@@ -69,8 +72,8 @@ export default function addComposerAutocomplete() {
dropdown.hide(); dropdown.hide();
dropdown.active = false; dropdown.active = false;
if (mentionStart) { if (absMentionStart) {
typed = value.substring(mentionStart, cursor).toLowerCase(); typed = lastChunk.substring(relMentionStart).toLowerCase();
const makeSuggestion = function(user, replacement, content, className = '') { const makeSuggestion = function(user, replacement, content, className = '') {
const username = usernameHelper(user); const username = usernameHelper(user);
@@ -100,7 +103,7 @@ export default function addComposerAutocomplete() {
user.displayName() user.displayName()
]; ];
return names.some(value => value.toLowerCase().substr(0, typed.length) === typed); return names.some(name => name.toLowerCase().substr(0, typed.length) === typed);
}; };
const buildSuggestions = () => { const buildSuggestions = () => {
@@ -153,18 +156,25 @@ export default function addComposerAutocomplete() {
m.render($container[0], dropdown.render()); m.render($container[0], dropdown.render());
dropdown.show(); dropdown.show();
const coordinates = getCaretCoordinates(this, mentionStart); const coordinates = app.composer.editor.getCaretCoordinates(absMentionStart);
const width = dropdown.$().outerWidth(); const width = dropdown.$().outerWidth();
const height = dropdown.$().outerHeight(); const height = dropdown.$().outerHeight();
const parent = dropdown.$().offsetParent(); const parent = dropdown.$().offsetParent();
let left = coordinates.left; let left = coordinates.left;
let top = coordinates.top - this.scrollTop + 15; let top = coordinates.top + 15;
// Keep the dropdown inside the editor.
if (top + height > parent.height()) { if (top + height > parent.height()) {
top = coordinates.top - this.scrollTop - height - 15; top = coordinates.top - height - 15;
} }
if (left + width > parent.width()) { if (left + width > parent.width()) {
left = parent.width() - width; left = parent.width() - width;
} }
// Prevent the dropdown from going off screen on mobile
top = Math.max(-parent.offset().top, top);
left = Math.max(-parent.offset().left, left);
dropdown.show(left, top); dropdown.show(left, top);
} else { } else {
dropdown.active = false; dropdown.active = false;

View File

@@ -20,7 +20,8 @@ function insertMention(post, composer, quote) {
Array(precedingNewlines).join('\n') + // Insert up to two newlines, depending on preceding whitespace Array(precedingNewlines).join('\n') + // Insert up to two newlines, depending on preceding whitespace
(quote (quote
? '> ' + mention + quote.trim().replace(/\n/g, '\n> ') + '\n\n' ? '> ' + mention + quote.trim().replace(/\n/g, '\n> ') + '\n\n'
: mention) : mention),
false
); );
} }