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

Minor refactors in frontend JS (#69)

* Add Prettier

* Update dependencies; add typescript setup

* Add tsconfig

* Rewrite some mentions frontend into TS

* Fix use of username instead of display name

* Change back to JS

* Remove commented code

* Update function name to match filename

* Update getMentionText.js

* Simplify condition

* Bump packages to stable versions; use prettier package

* Make functions use camel case

* Update js/package.json

Co-authored-by: Sami Mazouz <sychocouldy@gmail.com>

* Don't access data directly

* Update js/src/forum/addComposerAutocomplete.js

Co-authored-by: Alexander Skvortsov <38059171+askvortsov1@users.noreply.github.com>

* Update tsconfig.json

Co-authored-by: Sami Mazouz <sychocouldy@gmail.com>
Co-authored-by: Alexander Skvortsov <38059171+askvortsov1@users.noreply.github.com>
This commit is contained in:
David Wheatley
2021-09-20 18:42:38 +01:00
committed by GitHub
parent 6a5d7e9864
commit 6173c3d612
11 changed files with 4346 additions and 2725 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,20 @@
{ {
"private": true, "private": true,
"name": "@flarum/mentions", "name": "@flarum/mentions",
"prettier": "@flarum/prettier-config",
"dependencies": { "dependencies": {
"flarum-webpack-config": "0.1.0-beta.10", "flarum-webpack-config": "^1.0.0",
"webpack": "^4.43.0", "webpack": "^4.46.0",
"webpack-cli": "^3.3.11" "webpack-cli": "^4.7.2"
}, },
"scripts": { "scripts": {
"dev": "webpack --mode development --watch", "dev": "webpack --mode development --watch",
"build": "webpack --mode production" "build": "webpack --mode production",
"format": "prettier --write src"
},
"devDependencies": {
"@flarum/prettier-config": "^1.0.0",
"@types/mithril": "^2.0.8",
"flarum-tsconfig": "^1.0.0"
} }
} }

View File

@@ -1,22 +1,42 @@
import { extend } from 'flarum/extend'; import { extend } from 'flarum/common/extend';
import TextEditor from 'flarum/components/TextEditor'; import TextEditor from 'flarum/common/components/TextEditor';
import TextEditorButton from 'flarum/components/TextEditorButton'; import TextEditorButton from 'flarum/common/components/TextEditorButton';
import ReplyComposer from 'flarum/components/ReplyComposer'; import ReplyComposer from 'flarum/forum/components/ReplyComposer';
import EditPostComposer from 'flarum/components/EditPostComposer'; import EditPostComposer from 'flarum/forum/components/EditPostComposer';
import avatar from 'flarum/helpers/avatar'; import avatar from 'flarum/common/helpers/avatar';
import usernameHelper from 'flarum/helpers/username'; import usernameHelper from 'flarum/common/helpers/username';
import highlight from 'flarum/helpers/highlight'; import highlight from 'flarum/common/helpers/highlight';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable'; import KeyboardNavigatable from 'flarum/forum/utils/KeyboardNavigatable';
import { truncate } from 'flarum/utils/string'; import { truncate } from 'flarum/common/utils/string';
import { throttle } from 'flarum/common/utils/throttleDebounce';
import AutocompleteDropdown from './fragments/AutocompleteDropdown'; import AutocompleteDropdown from './fragments/AutocompleteDropdown';
import cleanDisplayName from './utils/cleanDisplayName'; import getMentionText from './utils/getMentionText';
const throttledSearch = throttle(
250, // 250ms timeout
function (typed, searched, returnedUsers, returnedUserIds, dropdown, buildSuggestions) {
const typedLower = typed.toLowerCase();
if (!searched.includes(typedLower)) {
app.store.find('users', { filter: { q: typed }, page: { limit: 5 } }).then((results) => {
results.forEach((u) => {
if (!returnedUserIds.has(u.id())) {
returnedUserIds.add(u.id());
returnedUsers.push(u);
}
});
if (dropdown.active) buildSuggestions();
});
searched.push(typedLower);
}
}
);
export default function addComposerAutocomplete() { export default function addComposerAutocomplete() {
const $container = $('<div class="ComposerBody-mentionsDropdownContainer"></div>'); const $container = $('<div class="ComposerBody-mentionsDropdownContainer"></div>');
const dropdown = new AutocompleteDropdown(); const dropdown = new AutocompleteDropdown();
extend(TextEditor.prototype, 'oncreate', function (params) { extend(TextEditor.prototype, 'oncreate', function () {
const $editor = this.$('.TextEditor-editor').wrap('<div class="ComposerBody-mentionsWrapper"></div>'); const $editor = this.$('.TextEditor-editor').wrap('<div class="ComposerBody-mentionsWrapper"></div>');
this.navigator = new KeyboardNavigatable(); this.navigator = new KeyboardNavigatable();
@@ -37,13 +57,12 @@ export default function addComposerAutocomplete() {
let absMentionStart; let absMentionStart;
let typed; let typed;
let matchTyped; let matchTyped;
let searchTimeout;
// We store users returned from an API here to preserve order in which they are returned // We store users returned from an API here to preserve order in which they are returned
// This prevents the user list jumping around while users are returned. // This prevents the user list jumping around while users are returned.
// We also use a hashset for user IDs to provide O(1) lookup for the users already in the list. // We also use a hashset for user IDs to provide O(1) lookup for the users already in the list.
const returnedUsers = Array.from(app.store.all('users')); const returnedUsers = Array.from(app.store.all('users'));
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(absMentionStart - 1, replacement + ' '); app.composer.editor.replaceBeforeCursor(absMentionStart - 1, replacement + ' ');
@@ -51,7 +70,7 @@ export default function addComposerAutocomplete() {
dropdown.hide(); dropdown.hide();
}; };
params.inputListeners.push(function(e) { params.inputListeners.push(function () {
const selection = app.composer.editor.getSelectionRange(); const selection = app.composer.editor.getSelectionRange();
const cursor = selection[0]; const cursor = selection[0];
@@ -81,33 +100,32 @@ export default function addComposerAutocomplete() {
const makeSuggestion = function (user, replacement, content, className = '') { const makeSuggestion = function (user, replacement, content, className = '') {
const username = usernameHelper(user); const username = usernameHelper(user);
if (typed) { if (typed) {
username.children = [highlight(username.text, typed)]; username.children = [highlight(username.text, typed)];
delete username.text; delete username.text;
} }
return ( return (
<button className={'PostPreview ' + className} <button
className={'PostPreview ' + className}
onclick={() => applySuggestion(replacement)} onclick={() => applySuggestion(replacement)}
onmouseenter={function () { onmouseenter={function () {
dropdown.setIndex($(this).parent().index()); dropdown.setIndex($(this).parent().index());
}}> }}
>
<span className="PostPreview-content"> <span className="PostPreview-content">
{avatar(user)} {avatar(user)}
{username} {' '} {username} {content}
{content}
</span> </span>
</button> </button>
); );
}; };
const userMatches = function (user) { const userMatches = function (user) {
const names = [ const names = [user.username(), user.displayName()];
user.username(),
user.displayName()
];
return names.some(name => name.toLowerCase().substr(0, typed.length) === typed); return names.some((name) => name.toLowerCase().substr(0, typed.length) === typed);
}; };
const buildSuggestions = () => { const buildSuggestions = () => {
@@ -116,12 +134,10 @@ export default function addComposerAutocomplete() {
// If the user has started to type a username, then suggest users // If the user has started to type a username, then suggest users
// matching that username. // matching that username.
if (typed) { if (typed) {
returnedUsers.forEach(user => { returnedUsers.forEach((user) => {
if (!userMatches(user)) return; if (!userMatches(user)) return;
suggestions.push( suggestions.push(makeSuggestion(user, getMentionText(user), '', 'MentionsDropdown-user'));
makeSuggestion(user, `@"${cleanDisplayName(user)}"#${user.id()}`, '', 'MentionsDropdown-user')
);
}); });
} }
@@ -135,21 +151,33 @@ export default function addComposerAutocomplete() {
const discussion = (composerPost && composerPost.discussion()) || composerAttrs.discussion; const discussion = (composerPost && composerPost.discussion()) || composerAttrs.discussion;
if (discussion) { if (discussion) {
discussion.posts() discussion
.filter(post => post && post.contentType() === 'comment' && (!composerPost || post.number() < composerPost.number())) .posts()
// Filter to only comment posts, and replies before this message
.filter((post) => post && post.contentType() === 'comment' && (!composerPost || post.number() < composerPost.number()))
// Sort by new to old
.sort((a, b) => b.createdAt() - a.createdAt()) .sort((a, b) => b.createdAt() - a.createdAt())
.filter(post => { // Filter to where the user matches what is being typed
.filter((post) => {
const user = post.user(); const user = post.user();
return user && userMatches(user); return user && userMatches(user);
}) })
// Get the first 5
.splice(0, 5) .splice(0, 5)
.forEach(post => { // Make the suggestions
.forEach((post) => {
const user = post.user(); const user = post.user();
suggestions.push( suggestions.push(
makeSuggestion(user, `@"${cleanDisplayName(user)}"#p${post.id()}`, [ makeSuggestion(
app.translator.trans('flarum-mentions.forum.composer.reply_to_post_text', {number: post.number()}), ' — ', user,
truncate(post.contentPlain(), 200) getMentionText(user, post.id()),
], 'MentionsDropdown-post') [
app.translator.trans('flarum-mentions.forum.composer.reply_to_post_text', { number: post.number() }),
' — ',
truncate(post.contentPlain(), 200),
],
'MentionsDropdown-post'
)
); );
}); });
} }
@@ -193,35 +221,21 @@ export default function addComposerAutocomplete() {
dropdown.setIndex(0); dropdown.setIndex(0);
dropdown.$().scrollTop(0); dropdown.$().scrollTop(0);
clearTimeout(searchTimeout);
// Don't send API calls searching for users until at least 2 characters have been typed. // Don't send API calls searching for users until at least 2 characters have been typed.
// This focuses the mention results on users and posts in the discussion. // This focuses the mention results on users and posts in the discussion.
if (typed.length > 1) { if (typed.length > 1) {
searchTimeout = setTimeout(function() { throttledSearch(typed, searched, returnedUsers, returnedUserIds, dropdown, buildSuggestions);
const typedLower = typed.toLowerCase();
if (searched.indexOf(typedLower) === -1) {
app.store.find('users', { filter: { q: typed }, page: { limit: 5 } }).then(results => {
results.forEach(u => {
if (!returnedUserIds.has(u.id())) {
returnedUserIds.add(u.id());
returnedUsers.push(u);
}
})
if (dropdown.active) buildSuggestions();
});
searched.push(typedLower);
}
}, 250);
} }
} }
}); });
}); });
extend(TextEditor.prototype, 'toolbarItems', function (items) { extend(TextEditor.prototype, 'toolbarItems', function (items) {
items.add('mention', ( items.add(
'mention',
<TextEditorButton onclick={() => this.attrs.composer.editor.insertAtCursor(' @')} icon="fas fa-at"> <TextEditorButton onclick={() => this.attrs.composer.editor.insertAtCursor(' @')} icon="fas fa-at">
{app.translator.trans('flarum-mentions.forum.composer.mention_tooltip')} {app.translator.trans('flarum-mentions.forum.composer.mention_tooltip')}
</TextEditorButton> </TextEditorButton>
)); );
}); });
} }

View File

@@ -1,12 +1,12 @@
import { extend } from 'flarum/extend'; import { extend } from 'flarum/common/extend';
import Model from 'flarum/Model'; import Model from 'flarum/common/Model';
import Post from 'flarum/models/Post'; import Post from 'flarum/common/models/Post';
import CommentPost from 'flarum/components/CommentPost'; import CommentPost from 'flarum/forum/components/CommentPost';
import Link from 'flarum/components/Link'; import Link from 'flarum/common/components/Link';
import PostPreview from 'flarum/components/PostPreview'; import PostPreview from 'flarum/forum/components/PostPreview';
import punctuateSeries from 'flarum/helpers/punctuateSeries'; import punctuateSeries from 'flarum/common/helpers/punctuateSeries';
import username from 'flarum/helpers/username'; import username from 'flarum/common/helpers/username';
import icon from 'flarum/helpers/icon'; import icon from 'flarum/common/helpers/icon';
export default function addMentionedByList() { export default function addMentionedByList() {
Post.prototype.mentionedBy = Model.hasMany('mentionedBy'); Post.prototype.mentionedBy = Model.hasMany('mentionedBy');
@@ -14,7 +14,9 @@ export default function addMentionedByList() {
function hidePreview() { function hidePreview() {
this.$('.Post-mentionedBy-preview') this.$('.Post-mentionedBy-preview')
.removeClass('in') .removeClass('in')
.one('transitionend', function() { $(this).hide(); }); .one('transitionend', function () {
$(this).hide();
});
} }
extend(CommentPost.prototype, 'oncreate', function () { extend(CommentPost.prototype, 'oncreate', function () {
@@ -35,22 +37,26 @@ export default function addMentionedByList() {
// When the user hovers their mouse over the list of people who have // When the user hovers their mouse over the list of people who have
// replied to the post, render a list of reply previews into a // replied to the post, render a list of reply previews into a
// popup. // popup.
m.render($preview[0], replies.map(reply => ( m.render(
$preview[0],
replies.map((reply) => (
<li data-number={reply.number()}> <li data-number={reply.number()}>
{PostPreview.component({ {PostPreview.component({
post: reply, post: reply,
onclick: hidePreview.bind(this) onclick: hidePreview.bind(this),
})} })}
</li> </li>
))); ))
);
$preview.show() $preview
.show()
.css('top', $this.offset().top - $parentPost.offset().top + $this.outerHeight(true)) .css('top', $this.offset().top - $parentPost.offset().top + $this.outerHeight(true))
.css('left', $this.offsetParent().offset().left - $parentPost.offset().left) .css('left', $this.offsetParent().offset().left - $parentPost.offset().left)
.css('max-width', $parentPost.width()); .css('max-width', $parentPost.width());
setTimeout(() => $preview.off('transitionend').addClass('in')); setTimeout(() => $preview.off('transitionend').addClass('in'));
} };
$this.add($preview).hover( $this.add($preview).hover(
() => { () => {
@@ -66,11 +72,16 @@ export default function addMentionedByList() {
// Whenever the user hovers their mouse over a particular name in the // Whenever the user hovers their mouse over a particular name in the
// list of repliers, highlight the corresponding post in the preview // list of repliers, highlight the corresponding post in the preview
// popup. // popup.
this.$().find('.Post-mentionedBy-summary a').hover(function() { this.$()
.find('.Post-mentionedBy-summary a')
.hover(
function () {
$preview.find('[data-number="' + $(this).data('number') + '"]').addClass('active'); $preview.find('[data-number="' + $(this).data('number') + '"]').addClass('active');
}, function() { },
function () {
$preview.find('[data-number]').removeClass('active'); $preview.find('[data-number]').removeClass('active');
}); }
);
} }
}); });
@@ -81,8 +92,8 @@ export default function addMentionedByList() {
if (replies && replies.length) { if (replies && replies.length) {
const users = []; const users = [];
const repliers = replies const repliers = replies
.sort(reply => reply.user() === app.session.user ? -1 : 0) .sort((reply) => (reply.user() === app.session.user ? -1 : 0))
.filter(reply => { .filter((reply) => {
const user = reply.user(); const user = reply.user();
if (users.indexOf(user) === -1) { if (users.indexOf(user) === -1) {
users.push(user); users.push(user);
@@ -95,15 +106,11 @@ export default function addMentionedByList() {
// Create a list of unique users who have replied. So even if a user has // Create a list of unique users who have replied. So even if a user has
// replied twice, they will only be in this array once. // replied twice, they will only be in this array once.
const names = repliers const names = repliers.slice(0, overLimit ? limit - 1 : limit).map((reply) => {
.slice(0, overLimit ? limit - 1 : limit)
.map(reply => {
const user = reply.user(); const user = reply.user();
return ( return (
<Link href={app.route.post(reply)} <Link href={app.route.post(reply)} onclick={hidePreview.bind(this)} data-number={reply.number()}>
onclick={hidePreview.bind(this)}
data-number={reply.number()}>
{app.session.user === user ? app.translator.trans('flarum-mentions.forum.post.you_text') : username(user)} {app.session.user === user ? app.translator.trans('flarum-mentions.forum.post.you_text') : username(user)}
</Link> </Link>
); );
@@ -115,18 +122,17 @@ export default function addMentionedByList() {
if (overLimit) { if (overLimit) {
const count = repliers.length - names.length; const count = repliers.length - names.length;
names.push( names.push(app.translator.trans('flarum-mentions.forum.post.others_text', { count }));
app.translator.trans('flarum-mentions.forum.post.others_text', {count})
);
} }
items.add('replies', items.add(
'replies',
<div className="Post-mentionedBy"> <div className="Post-mentionedBy">
<span className="Post-mentionedBy-summary"> <span className="Post-mentionedBy-summary">
{icon('fas fa-reply')} {icon('fas fa-reply')}
{app.translator.trans('flarum-mentions.forum.post.mentioned_by' + (repliers[0].user() === app.session.user ? '_self' : '') + '_text', { {app.translator.trans('flarum-mentions.forum.post.mentioned_by' + (repliers[0].user() === app.session.user ? '_self' : '') + '_text', {
count: names.length, count: names.length,
users: punctuateSeries(names) users: punctuateSeries(names),
})} })}
</span> </span>
</div> </div>

View File

@@ -1,7 +1,7 @@
import { extend } from 'flarum/extend'; import { extend } from 'flarum/common/extend';
import CommentPost from 'flarum/components/CommentPost'; import CommentPost from 'flarum/forum/components/CommentPost';
import PostPreview from 'flarum/components/PostPreview'; import PostPreview from 'flarum/forum/components/PostPreview';
import LoadingIndicator from 'flarum/components/LoadingIndicator'; import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
export default function addPostMentionPreviews() { export default function addPostMentionPreviews() {
function addPreviews() { function addPreviews() {
@@ -65,20 +65,25 @@ export default function addPostMentionPreviews() {
offset -= previewHeight; offset -= previewHeight;
} }
$preview.show() $preview
.show()
.css('top', $this.offset().top - $parentPost.offset().top + offset) .css('top', $this.offset().top - $parentPost.offset().top + offset)
.css('left', $this.offsetParent().offset().left - $parentPost.offset().left) .css('left', $this.offsetParent().offset().left - $parentPost.offset().left)
.css('max-width', $this.offsetParent().width()); .css('max-width', $this.offsetParent().width());
}; };
const showPost = post => { const showPost = (post) => {
const discussion = post.discussion(); const discussion = post.discussion();
m.render($preview[0], [ m.render($preview[0], [
discussion !== parentPost.discussion() discussion !== parentPost.discussion() ? (
? <li><span className="PostMention-preview-discussion">{discussion.title()}</span></li> <li>
: '', <span className="PostMention-preview-discussion">{discussion.title()}</span>
<li>{PostPreview.component({post})}</li> </li>
) : (
''
),
<li>{PostPreview.component({ post })}</li>,
]); ]);
positionPreview(); positionPreview();
}; };
@@ -106,13 +111,15 @@ export default function addPostMentionPreviews() {
// On a touch (mobile) device we cannot hover the link to reveal the preview. // On a touch (mobile) device we cannot hover the link to reveal the preview.
// Instead we cancel the navigation so that a click reveals the preview. // Instead we cancel the navigation so that a click reveals the preview.
// Users can then click on the preview to go to the post if desired. // Users can then click on the preview to go to the post if desired.
$this.on('touchend', e => { $this.on('touchend', (e) => {
if (e.cancelable) { if (e.cancelable) {
e.preventDefault(); e.preventDefault();
} }
}); });
$this.add($preview).hover( $this
.add($preview)
.hover(
() => { () => {
clearTimeout(timeout); clearTimeout(timeout);
timeout = setTimeout(showPreview, 250); timeout = setTimeout(showPreview, 250);
@@ -123,7 +130,7 @@ export default function addPostMentionPreviews() {
timeout = setTimeout(hidePreview, 250); timeout = setTimeout(hidePreview, 250);
} }
) )
.on('touchend', e => { .on('touchend', (e) => {
showPreview(); showPreview();
e.stopPropagation(); e.stopPropagation();
}); });

View File

@@ -1,3 +0,0 @@
export default function cleanDisplayName(user) {
return user.displayName().replace(/"#[a-z]{0,3}[0-9]+/, '_');
};

View File

@@ -0,0 +1,24 @@
/**
* Whether to use the old mentions format.
*
* `'@username'` or `'@"Display name"'`
*/
export const shouldUseOldFormat = () => app.forum.attribute('allowUsernameMentionFormat') || false;
const getDeletedUserText = () => app.translator.trans('core.lib.username.deleted_text');
/**
* Fetches a user's username or display name.
*
* Chooses based on the format option set in the admin settings page.
*
* @param user An instance of the User model to fetch the username for
* @param useDisplayName If `true`, uses `user.displayName()`, otherwise, uses `user.username()`
*/
export default function getCleanDisplayName(user, useDisplayName = true) {
if (!user) return getDeletedUserText().replace(/"#[a-z]{0,3}[0-9]+/, '_');
const text = (useDisplayName ? user.displayName() : user.username()) || getDeletedUserText();
return text.replace(/"#[a-z]{0,3}[0-9]+/, '_');
}

View File

@@ -0,0 +1,36 @@
import getCleanDisplayName, { ShouldUseOldFormat } from './getCleanDisplayName';
/**
* Fetches the mention text for a specified user (and optionally a post ID for replies).
*
* Automatically determines which mention syntax to be used based on the option in the
* admin dashboard. Also performs display name clean-up automatically.
*
* @example <caption>New display name syntax</caption>
* // '@"User"#1'
* getMentionText(User) // User is ID 1, display name is 'User'
*
* @example <caption>Replying</caption>
* // '@"User"#p13'
* getMentionText(User, 13) // User display name is 'User', post ID is 13
*
* @example <caption>Using old syntax</caption>
* // '@username'
* getMentionText(User) // User's username is 'username'
*/
export default function getMentionText(user, postId) {
if (postId === undefined) {
if (ShouldUseOldFormat()) {
// Plain @username
const cleanText = getCleanDisplayName(user, false);
return `@${cleanText}`;
}
// @"Display name"#UserID
const cleanText = getCleanDisplayName(user);
return `@"${cleanText}"${user.id()}`;
} else {
// @"Display name"#pPostID
const cleanText = getCleanDisplayName(user);
return `@"${cleanText}"#p${postId}`;
}
}

View File

@@ -1,10 +1,10 @@
import DiscussionControls from 'flarum/utils/DiscussionControls'; import DiscussionControls from 'flarum/forum/utils/DiscussionControls';
import EditPostComposer from 'flarum/components/EditPostComposer'; import EditPostComposer from 'flarum/forum/components/EditPostComposer';
import cleanDisplayName from './cleanDisplayName'; import getMentionText from './getMentionText';
function insertMention(post, composer, quote) { function insertMention(post, composer, quote) {
const user = post.user(); const user = post.user();
const mention = `@"${(user && cleanDisplayName(user)) || app.translator.trans('core.lib.username.deleted_text')}"#p${post.id()} `; const mention = getMentionText(user, post.id());
// If the composer is empty, then assume we're starting a new reply. // If the composer is empty, then assume we're starting a new reply.
// In which case we don't want the user to have to confirm if they // In which case we don't want the user to have to confirm if they
@@ -19,9 +19,7 @@ function insertMention(post, composer, quote) {
composer.editor.insertAtCursor( composer.editor.insertAtCursor(
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),
? '> ' + mention + quote.trim().replace(/\n/g, '\n> ') + '\n\n'
: mention),
false false
); );
} }
@@ -35,7 +33,6 @@ export default function reply(post, quote) {
// The default "Reply" action behavior will only open a new composer if // The default "Reply" action behavior will only open a new composer if
// necessary, but it will always be a ReplyComposer, hence the exceptional // necessary, but it will always be a ReplyComposer, hence the exceptional
// case above. // case above.
DiscussionControls.replyAction.call(post.discussion()) DiscussionControls.replyAction.call(post.discussion()).then((composer) => insertMention(post, composer, quote));
.then(composer => insertMention(post, composer, quote));
} }
} }

View File

@@ -1,10 +1,15 @@
/**
* Finds the selected text in the provided composer body.
*/
export default function selectedText(body) { export default function selectedText(body) {
const selection = window.getSelection(); const selection = window.getSelection();
if (selection.rangeCount) {
if (selection?.rangeCount) {
const range = selection.getRangeAt(0); const range = selection.getRangeAt(0);
const parent = range.commonAncestorContainer; const parent = range.commonAncestorContainer;
if (body[0] === parent || $.contains(body[0], parent)) { if (body[0] === parent || $.contains(body[0], parent)) {
const clone = $("<div>").append(range.cloneContents()); const clone = $('<div>').append(range.cloneContents());
// Replace emoji images with their shortcode (found in alt attribute) // Replace emoji images with their shortcode (found in alt attribute)
clone.find('img.emoji').replaceWith(function () { clone.find('img.emoji').replaceWith(function () {
@@ -13,16 +18,16 @@ export default function selectedText(body) {
// Replace all other images with a Markdown image // Replace all other images with a Markdown image
clone.find('img').replaceWith(function () { clone.find('img').replaceWith(function () {
return '![](' + this.src + ')'; return `![](${this.src})`;
}); });
// Replace all links with a Markdown link // Replace all links with a Markdown link
clone.find('a').replaceWith(function () { clone.find('a').replaceWith(function () {
return '[' + this.innerText + '](' + this.href + ')'; return `[${this.innerText}](${this.href})`;
}); });
return clone.text(); return clone.text();
} }
} }
return ""; return '';
} }

View File

@@ -0,0 +1,16 @@
{
// Use Flarum's tsconfig as a starting point
"extends": "flarum-tsconfig",
// This will match all .ts, .tsx, .d.ts, .js, .jsx files in your `src` folder
// and also tells your Typescript server to read core's global typings for
// access to `dayjs` and `$` in the global namespace.
"include": ["src/**/*", "../vendor/flarum/core/js/dist-typings/@types/**/*"],
"compilerOptions": {
// This will output typings to `dist-typings`
"declarationDir": "./dist-typings",
"baseUrl": ".",
"paths": {
"flarum/*": ["../vendor/flarum/core/js/dist-typings/*"]
}
}
}