mirror of
https://github.com/flarum/core.git
synced 2025-08-06 08:27:42 +02:00
Update for Editor Drivers Abstraction (#34)
This commit is contained in:
committed by
GitHub
parent
5c1fa4aea8
commit
720ea6c576
37
extensions/emoji/js/package-lock.json
generated
37
extensions/emoji/js/package-lock.json
generated
@@ -1778,9 +1778,9 @@
|
||||
"integrity": "sha512-rYWlogJ2q5P78U8Xx1vhsXHcYKu1wFnr7+o6z9QHssZ1SsJLTCkJINZIPHRFWuDreAUK457TkqHpdOXElu0fzA=="
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"requires": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
@@ -3441,9 +3441,12 @@
|
||||
"integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
||||
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ=="
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
|
||||
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
|
||||
"requires": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
@@ -3500,7 +3503,8 @@
|
||||
},
|
||||
"simple-emoji-map": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "git+https://github.com/davwheat/simple-emoji-map.git#dfa29dae25b4620302b1330cf54e12d4492fedf0",
|
||||
"resolved": "https://registry.npmjs.org/simple-emoji-map/-/simple-emoji-map-0.4.1.tgz",
|
||||
"integrity": "sha512-K40UJhKs0VGE4gXj4cV/REnBLIUfPJ5n+eLWwpaOWQmjnJnK1+uyw/KIqFAWemD1kpsL2/khUNYRLe48YH9MKA==",
|
||||
"requires": {
|
||||
"cosmiconfig": "^6.0.0",
|
||||
"emojibase-data": "^5.0.1",
|
||||
@@ -3770,9 +3774,9 @@
|
||||
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
|
||||
},
|
||||
"terser": {
|
||||
"version": "4.6.13",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.6.13.tgz",
|
||||
"integrity": "sha512-wMvqukYgVpQlymbnNbabVZbtM6PN63AzqexpwJL8tbh/mRT9LE5o+ruVduAGL7D6Fpjl+Q+06U5I9Ul82odAhw==",
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
|
||||
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
@@ -3787,15 +3791,15 @@
|
||||
}
|
||||
},
|
||||
"terser-webpack-plugin": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
|
||||
"integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
|
||||
"integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
|
||||
"requires": {
|
||||
"cacache": "^12.0.2",
|
||||
"find-cache-dir": "^2.1.0",
|
||||
"is-wsl": "^1.1.0",
|
||||
"schema-utils": "^1.0.0",
|
||||
"serialize-javascript": "^2.1.2",
|
||||
"serialize-javascript": "^4.0.0",
|
||||
"source-map": "^0.6.1",
|
||||
"terser": "^4.1.2",
|
||||
"webpack-sources": "^1.4.0",
|
||||
@@ -3809,11 +3813,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": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
|
||||
|
@@ -4,7 +4,6 @@
|
||||
"dependencies": {
|
||||
"flarum-webpack-config": "0.1.0-beta.10",
|
||||
"simple-emoji-map": "^0.4.1",
|
||||
"textarea-caret": "^3.1.0",
|
||||
"twemoji": "^13.0.0",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.12"
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import getCaretCoordinates from 'textarea-caret';
|
||||
import emojiMap from 'simple-emoji-map';
|
||||
|
||||
import { extend } from 'flarum/extend';
|
||||
@@ -12,20 +11,11 @@ import cdn from './cdn';
|
||||
|
||||
export default function addComposerAutocomplete() {
|
||||
const emojiKeys = Object.keys(emojiMap);
|
||||
const $container = $('<div class="ComposerBody-emojiDropdownContainer"></div>');
|
||||
const dropdown = new AutocompleteDropdown();
|
||||
|
||||
extend(TextEditor.prototype, 'oncreate', function() {
|
||||
|
||||
const $container = $('<div class="ComposerBody-emojiDropdownContainer"></div>');
|
||||
const dropdown = new AutocompleteDropdown();
|
||||
const $textarea = this.$('textarea').wrap('<div class="ComposerBody-emojiWrapper"></div>');
|
||||
let emojiStart;
|
||||
let typed;
|
||||
|
||||
const applySuggestion = (replacement) => {
|
||||
this.attrs.composer.editor.replaceBeforeCursor(emojiStart - 1, replacement + ' ');
|
||||
|
||||
dropdown.hide();
|
||||
};
|
||||
extend(TextEditor.prototype, 'oncreate', function () {
|
||||
const $editor = this.$('.TextEditor-editor').wrap('<div class="ComposerBody-emojiWrapper"></div>');
|
||||
|
||||
this.navigator = new KeyboardNavigatable();
|
||||
this.navigator
|
||||
@@ -34,131 +24,149 @@ export default function addComposerAutocomplete() {
|
||||
.onDown(() => dropdown.navigate(1))
|
||||
.onSelect(dropdown.complete.bind(dropdown))
|
||||
.onCancel(dropdown.hide.bind(dropdown))
|
||||
.bindTo($textarea);
|
||||
.bindTo($editor);
|
||||
|
||||
$textarea
|
||||
.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;
|
||||
$editor.after($container);
|
||||
|
||||
const cursor = this.selectionStart;
|
||||
});
|
||||
|
||||
if (this.selectionEnd - cursor > 0) return;
|
||||
extend(TextEditor.prototype, 'buildEditorParams', function (params) {
|
||||
let relEmojiStart;
|
||||
let absEmojiStart;
|
||||
let typed;
|
||||
|
||||
// Search backwards from the cursor for an ':' symbol. If we find
|
||||
// one and followed by a whitespace, we will want to show the
|
||||
// autocomplete dropdown!
|
||||
const value = this.value;
|
||||
emojiStart = 0;
|
||||
for (let i = cursor - 1; i >= 0; i--) {
|
||||
const character = value.substr(i, 1);
|
||||
// check what user typed, emoji names only contains alphanumeric,
|
||||
// underline, '+' and '-'
|
||||
if (!/[a-z0-9]|\+|\-|_|\:/.test(character)) break;
|
||||
// make sure ':' followed by a whitespace or newline
|
||||
if (character === ':' && (i == 0 || /\s/.test(value.substr(i - 1, 1)))) {
|
||||
emojiStart = i + 1;
|
||||
break;
|
||||
}
|
||||
const applySuggestion = (replacement) => {
|
||||
this.attrs.composer.editor.replaceBeforeCursor(absEmojiStart - 1, replacement + ' ');
|
||||
|
||||
dropdown.hide();
|
||||
};
|
||||
|
||||
params.inputListeners.push(function (e) {
|
||||
const selection = app.composer.editor.getSelectionRange();
|
||||
|
||||
const cursor = selection[0];
|
||||
|
||||
if (selection[1] - cursor > 0) return;
|
||||
|
||||
// Search backwards from the cursor for an ':' symbol. If we find
|
||||
// one and followed by a whitespace, we will want to show the
|
||||
// autocomplete dropdown!
|
||||
const lastChunk = app.composer.editor.getLastNChars(15);
|
||||
absEmojiStart = 0;
|
||||
for (let i = lastChunk.length - 1; i >= 0; i--) {
|
||||
const character = lastChunk.substr(i, 1);
|
||||
// check what user typed, emoji names only contains alphanumeric,
|
||||
// underline, '+' and '-'
|
||||
if (!/[a-z0-9]|\+|\-|_|\:/.test(character)) break;
|
||||
// make sure ':' preceded by a whitespace or newline
|
||||
if (character === ':' && (i == 0 || /\s/.test(lastChunk.substr(i - 1, 1)))) {
|
||||
relEmojiStart = i + 1;
|
||||
absEmojiStart = cursor - lastChunk.length + i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dropdown.hide();
|
||||
dropdown.active = false;
|
||||
dropdown.hide();
|
||||
dropdown.active = false;
|
||||
|
||||
if (emojiStart) {
|
||||
typed = value.substring(emojiStart, cursor).toLowerCase();
|
||||
if (absEmojiStart) {
|
||||
typed = lastChunk.substring(relEmojiStart).toLowerCase();
|
||||
|
||||
const makeSuggestion = function({emoji, name, code}) {
|
||||
return (
|
||||
<button
|
||||
key={emoji}
|
||||
onclick={() => applySuggestion(emoji)}
|
||||
onmouseenter={function() {
|
||||
dropdown.setIndex($(this).parent().index() - 1);
|
||||
}}>
|
||||
<img alt={emoji} class="emoji" draggable="false" loading="lazy" src={`${cdn}72x72/${code}.png`}/>
|
||||
{name}
|
||||
</button>
|
||||
);
|
||||
const makeSuggestion = function ({ emoji, name, code }) {
|
||||
return (
|
||||
<button
|
||||
key={emoji}
|
||||
onclick={() => applySuggestion(emoji)}
|
||||
onmouseenter={function () {
|
||||
dropdown.setIndex($(this).parent().index() - 1);
|
||||
}}>
|
||||
<img alt={emoji} class="emoji" draggable="false" loading="lazy" src={`${cdn}72x72/${code}.png`} />
|
||||
{name}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const buildSuggestions = () => {
|
||||
const similarEmoji = [];
|
||||
|
||||
// Build a regular expression to do a fuzzy match of the given input string
|
||||
const fuzzyRegexp = function (str) {
|
||||
const reEscape = new RegExp('\\(([' + ('+.*?[]{}()^$|\\'.replace(/(.)/g, '\\$1')) + '])\\)', 'g');
|
||||
return new RegExp('(.*)' + (str.toLowerCase().replace(/(.)/g, '($1)(.*?)')).replace(reEscape, '(\\$1)') + '$', 'i');
|
||||
};
|
||||
const regTyped = fuzzyRegexp(typed);
|
||||
|
||||
const buildSuggestions = () => {
|
||||
const similarEmoji = [];
|
||||
let maxSuggestions = 7;
|
||||
|
||||
// Build a regular expression to do a fuzzy match of the given input string
|
||||
const fuzzyRegexp = function(str) {
|
||||
const reEscape = new RegExp('\\(([' + ('+.*?[]{}()^$|\\'.replace(/(.)/g, '\\$1')) + '])\\)', 'g');
|
||||
return new RegExp('(.*)' + (str.toLowerCase().replace(/(.)/g, '($1)(.*?)')).replace(reEscape, '(\\$1)') + '$', 'i');
|
||||
};
|
||||
const regTyped = fuzzyRegexp(typed);
|
||||
const findMatchingEmojis = matcher => {
|
||||
for (let i = 0; i < emojiKeys.length && maxSuggestions > 0; i++) {
|
||||
const curEmoji = emojiKeys[i];
|
||||
|
||||
let maxSuggestions = 7;
|
||||
|
||||
const findMatchingEmojis = matcher => {
|
||||
for (let i = 0; i < emojiKeys.length && maxSuggestions > 0; i++) {
|
||||
const curEmoji = emojiKeys[i];
|
||||
|
||||
if (similarEmoji.indexOf(curEmoji) === -1) {
|
||||
const names = emojiMap[curEmoji];
|
||||
for (let name of names) {
|
||||
if (matcher(name)) {
|
||||
--maxSuggestions;
|
||||
similarEmoji.push(curEmoji);
|
||||
break;
|
||||
}
|
||||
if (similarEmoji.indexOf(curEmoji) === -1) {
|
||||
const names = emojiMap[curEmoji];
|
||||
for (let name of names) {
|
||||
if (matcher(name)) {
|
||||
--maxSuggestions;
|
||||
similarEmoji.push(curEmoji);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// First, try to find all emojis starting with the given string
|
||||
findMatchingEmojis(emoji => emoji.indexOf(typed) === 0);
|
||||
|
||||
// If there are still suggestions left, try for some fuzzy matches
|
||||
findMatchingEmojis(emoji => regTyped.test(emoji));
|
||||
|
||||
const suggestions = similarEmoji.map(emoji => ({
|
||||
emoji,
|
||||
name: emojiMap[emoji][0],
|
||||
code: getEmojiIconCode(emoji),
|
||||
})).map(makeSuggestion);
|
||||
|
||||
if (suggestions.length) {
|
||||
dropdown.items = suggestions;
|
||||
m.render($container[0], dropdown.render());
|
||||
|
||||
dropdown.show();
|
||||
const coordinates = getCaretCoordinates(this, emojiStart);
|
||||
const width = dropdown.$().outerWidth();
|
||||
const height = dropdown.$().outerHeight();
|
||||
const parent = dropdown.$().offsetParent();
|
||||
let left = coordinates.left;
|
||||
let top = coordinates.top + 15;
|
||||
if (top + height > parent.height()) {
|
||||
top = coordinates.top - height - 15;
|
||||
}
|
||||
if (left + width > parent.width()) {
|
||||
left = parent.width() - width;
|
||||
}
|
||||
top = Math.max(-$(this).offset().top, top);
|
||||
left = Math.max(-$(this).offset().left, left);
|
||||
dropdown.show(left, top);
|
||||
}
|
||||
};
|
||||
|
||||
buildSuggestions();
|
||||
// First, try to find all emojis starting with the given string
|
||||
findMatchingEmojis(emoji => emoji.indexOf(typed) === 0);
|
||||
|
||||
dropdown.setIndex(0);
|
||||
dropdown.$().scrollTop(0);
|
||||
dropdown.active = true;
|
||||
}
|
||||
});
|
||||
// If there are still suggestions left, try for some fuzzy matches
|
||||
findMatchingEmojis(emoji => regTyped.test(emoji));
|
||||
|
||||
const suggestions = similarEmoji.map(emoji => ({
|
||||
emoji,
|
||||
name: emojiMap[emoji][0],
|
||||
code: getEmojiIconCode(emoji),
|
||||
})).map(makeSuggestion);
|
||||
|
||||
if (suggestions.length) {
|
||||
dropdown.items = suggestions;
|
||||
m.render($container[0], dropdown.render());
|
||||
|
||||
dropdown.show();
|
||||
const coordinates = app.composer.editor.getCaretCoordinates(absEmojiStart);
|
||||
const width = dropdown.$().outerWidth();
|
||||
const height = dropdown.$().outerHeight();
|
||||
const parent = dropdown.$().offsetParent();
|
||||
let left = coordinates.left;
|
||||
let top = coordinates.top + 15;
|
||||
|
||||
// Keep the dropdown inside the editor.
|
||||
if (top + height > parent.height()) {
|
||||
top = coordinates.top - height - 15;
|
||||
}
|
||||
if (left + width > parent.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);
|
||||
}
|
||||
};
|
||||
|
||||
buildSuggestions();
|
||||
|
||||
dropdown.setIndex(0);
|
||||
dropdown.$().scrollTop(0);
|
||||
dropdown.active = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
extend(TextEditor.prototype, 'toolbarItems', function(items) {
|
||||
extend(TextEditor.prototype, 'toolbarItems', function (items) {
|
||||
items.add('emoji', (
|
||||
<TextEditorButton onclick={() => this.attrs.composer.editor.insertAtCursor(':')} icon="far fa-smile">
|
||||
<TextEditorButton onclick={() => this.attrs.composer.editor.insertAtCursor(' :')} icon="far fa-smile">
|
||||
{app.translator.trans('flarum-emoji.forum.composer.emoji_tooltip')}
|
||||
</TextEditorButton>
|
||||
));
|
||||
|
Reference in New Issue
Block a user