1
0
mirror of https://github.com/flarum/core.git synced 2025-08-16 21:34:08 +02:00
See https://github.com/flarum/core/pull/1367

* Replace gulp with webpack and npm scripts for JS compilation
* Set up Travis CI to commit compiled JS
* Restructure `js` directory; only one instance of npm, forum/admin are "submodules"
* Restructure `less` directory
This commit is contained in:
Toby Zerner
2018-06-20 13:35:33 +09:30
committed by GitHub
parent 87f2966bf9
commit d1f4d5a739
28 changed files with 4762 additions and 1387 deletions

View File

@@ -0,0 +1,192 @@
import getCaretCoordinates from 'textarea-caret';
import { extend } from 'flarum/extend';
import ComposerBody from 'flarum/components/ComposerBody';
import avatar from 'flarum/helpers/avatar';
import usernameHelper from 'flarum/helpers/username';
import highlight from 'flarum/helpers/highlight';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';
import { truncate } from 'flarum/utils/string';
import AutocompleteDropdown from './components/AutocompleteDropdown';
export default function addComposerAutocomplete() {
extend(ComposerBody.prototype, 'config', function(original, isInitialized) {
if (isInitialized) return;
const composer = this;
const $container = $('<div class="ComposerBody-mentionsDropdownContainer"></div>');
const dropdown = new AutocompleteDropdown({items: []});
const $textarea = this.$('textarea').wrap('<div class="ComposerBody-mentionsWrapper"></div>');
const searched = [];
let mentionStart;
let typed;
let searchTimeout;
const applySuggestion = function(replacement) {
const insert = replacement + ' ';
const content = composer.content();
composer.editor.setValue(content.substring(0, mentionStart - 1) + insert + content.substr($textarea[0].selectionStart));
const index = mentionStart - 1 + insert.length;
composer.editor.setSelectionRange(index, index);
dropdown.hide();
};
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($textarea);
$textarea
.after($container)
.on('click keyup', 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 (this.selectionEnd - cursor > 0) return;
// Search backwards from the cursor for an '@' symbol. If we find one,
// we will want to show the autocomplete dropdown!
const value = this.value;
mentionStart = 0;
for (let i = cursor - 1; i >= cursor - 30; i--) {
const character = value.substr(i, 1);
if (character === '@') {
mentionStart = i + 1;
break;
}
}
dropdown.hide();
dropdown.active = false;
if (mentionStart) {
typed = value.substring(mentionStart, cursor).toLowerCase();
const makeSuggestion = function(user, replacement, content, className = '') {
const username = usernameHelper(user);
if (typed) {
username.children[0] = highlight(username.children[0], typed);
}
return (
<button className={'PostPreview ' + className}
onclick={() => applySuggestion(replacement)}
onmouseenter={function() {
dropdown.setIndex($(this).parent().index());
}}>
<span className="PostPreview-content">
{avatar(user)}
{username} {' '}
{content}
</span>
</button>
);
};
const userMatches = function(user) {
const names = [
user.username(),
user.displayName()
];
return names.some(value => value.toLowerCase().substr(0, typed.length) === typed);
};
const buildSuggestions = () => {
const suggestions = [];
// If the user has started to type a username, then suggest users
// matching that username.
if (typed) {
app.store.all('users').forEach(user => {
if (!userMatches(user)) return;
suggestions.push(
makeSuggestion(user, '@' + user.username(), '', 'MentionsDropdown-user')
);
});
}
// If the user is replying to a discussion, or if they are editing a
// post, then we can suggest other posts in the discussion to mention.
// We will add the 5 most recent comments in the discussion which
// match any username characters that have been typed.
const composerPost = composer.props.post;
const discussion = (composerPost && composerPost.discussion()) || composer.props.discussion;
if (discussion) {
discussion.posts()
.filter(post => post && post.contentType() === 'comment' && (!composerPost || post.number() < composerPost.number()))
.sort((a, b) => b.time() - a.time())
.filter(post => {
const user = post.user();
return user && userMatches(user);
})
.splice(0, 5)
.forEach(post => {
const user = post.user();
suggestions.push(
makeSuggestion(user, '@' + user.username() + '#' + post.id(), [
app.translator.trans('flarum-mentions.forum.composer.reply_to_post_text', {number: post.number()}), ' — ',
truncate(post.contentPlain(), 200)
], 'MentionsDropdown-post')
);
});
}
if (suggestions.length) {
dropdown.props.items = suggestions;
m.render($container[0], dropdown.render());
dropdown.show();
const coordinates = getCaretCoordinates(this, mentionStart);
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;
}
dropdown.show(left, top);
} else {
dropdown.active = false;
dropdown.hide();
}
};
dropdown.active = true;
buildSuggestions();
dropdown.setIndex(0);
dropdown.$().scrollTop(0);
clearTimeout(searchTimeout);
if (typed) {
searchTimeout = setTimeout(function() {
const typedLower = typed.toLowerCase();
if (searched.indexOf(typedLower) === -1) {
app.store.find('users', {filter: {q: typed}, page: {limit: 5}}).then(() => {
if (dropdown.active) buildSuggestions();
});
searched.push(typedLower);
}
}, 250);
}
}
});
});
}

View File

@@ -0,0 +1,128 @@
import { extend } from 'flarum/extend';
import Model from 'flarum/Model';
import Post from 'flarum/models/Post';
import CommentPost from 'flarum/components/CommentPost';
import PostPreview from 'flarum/components/PostPreview';
import punctuateSeries from 'flarum/helpers/punctuateSeries';
import username from 'flarum/helpers/username';
import icon from 'flarum/helpers/icon';
export default function addMentionedByList() {
Post.prototype.mentionedBy = Model.hasMany('mentionedBy');
extend(CommentPost.prototype, 'footerItems', function(items) {
const post = this.props.post;
const replies = post.mentionedBy();
if (replies && replies.length) {
// If there is only one reply, and it's adjacent to this post, we don't
// really need to show the list.
if (replies.length === 1 && replies[0].number() === post.number() + 1) {
return;
}
const hidePreview = () => {
this.$('.Post-mentionedBy-preview')
.removeClass('in')
.one('transitionend', function() { $(this).hide(); });
};
const config = function(element, isInitialized) {
if (isInitialized) return;
const $this = $(element);
let timeout;
const $preview = $('<ul class="Dropdown-menu Post-mentionedBy-preview fade"/>');
$this.append($preview);
$this.children().hover(function() {
clearTimeout(timeout);
timeout = setTimeout(function() {
if (!$preview.hasClass('in') && $preview.is(':visible')) return;
// 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
// popup.
m.render($preview[0], replies.map(reply => (
<li data-number={reply.number()}>
{PostPreview.component({
post: reply,
onclick: hidePreview
})}
</li>
)));
$preview.show();
setTimeout(() => $preview.off('transitionend').addClass('in'));
}, 500);
}, function() {
clearTimeout(timeout);
timeout = setTimeout(hidePreview, 250);
});
// Whenever the user hovers their mouse over a particular name in the
// list of repliers, highlight the corresponding post in the preview
// popup.
$this.find('.Post-mentionedBy-summary a').hover(function() {
$preview.find('[data-number="' + $(this).data('number') + '"]').addClass('active');
}, function() {
$preview.find('[data-number]').removeClass('active');
});
};
const users = [];
const repliers = replies
.sort(reply => reply.user() === app.session.user ? -1 : 0)
.filter(reply => {
const user = reply.user();
if (users.indexOf(user) === -1) {
users.push(user);
return true;
}
});
const limit = 4;
const overLimit = repliers.length > limit;
// 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.
const names = repliers
.slice(0, overLimit ? limit - 1 : limit)
.map(reply => {
const user = reply.user();
return (
<a href={app.route.post(reply)}
config={m.route}
onclick={hidePreview}
data-number={reply.number()}>
{app.session.user === user ? app.translator.trans('flarum-mentions.forum.post.you_text') : username(user)}
</a>
);
});
// If there are more users that we've run out of room to display, add a "x
// others" name to the end of the list. Clicking on it will display a modal
// with a full list of names.
if (overLimit) {
const count = repliers.length - names.length;
names.push(
app.translator.transChoice('flarum-mentions.forum.post.others_text', count, {count})
);
}
items.add('replies',
<div className="Post-mentionedBy" config={config}>
<span className="Post-mentionedBy-summary">
{icon('fas fa-reply')}
{app.translator.transChoice('flarum-mentions.forum.post.mentioned_by' + (repliers[0].user() === app.session.user ? '_self' : '') + '_text', names.length, {
count: names.length,
users: punctuateSeries(names)
})}
</span>
</div>
);
}
});
}

View File

@@ -0,0 +1,126 @@
import { extend } from 'flarum/extend';
import CommentPost from 'flarum/components/CommentPost';
import PostPreview from 'flarum/components/PostPreview';
import LoadingIndicator from 'flarum/components/LoadingIndicator';
export default function addPostMentionPreviews() {
extend(CommentPost.prototype, 'config', function() {
const contentHtml = this.props.post.contentHtml();
if (contentHtml === this.oldPostContentHtml || this.isEditing()) return;
this.oldPostContentHtml = contentHtml;
const parentPost = this.props.post;
const $parentPost = this.$();
this.$('.UserMention, .PostMention').each(function() {
m.route.call(this, this, false, {}, {attrs: {href: this.getAttribute('href')}});
});
this.$('.PostMention').each(function() {
const $this = $(this);
const id = $this.data('id');
let timeout;
// Wrap the mention link in a wrapper element so that we can insert a
// preview popup as its sibling and relatively position it.
const $preview = $('<ul class="Dropdown-menu PostMention-preview fade"/>');
$parentPost.append($preview);
const getPostElement = () => {
return $(`.PostStream-item[data-id="${id}"]`);
};
const showPreview = () => {
// When the user hovers their mouse over the mention, look for the
// post that it's referring to in the stream, and determine if it's
// in the viewport. If it is, we will "pulsate" it.
const $post = getPostElement();
let visible = false;
if ($post.length) {
const top = $post.offset().top;
const scrollTop = window.pageYOffset;
if (top > scrollTop && top + $post.height() < scrollTop + $(window).height()) {
$post.addClass('pulsate');
visible = true;
}
}
// Otherwise, we will show a popup preview of the post. If the post
// hasn't yet been loaded, we will need to do that.
if (!visible) {
// Position the preview so that it appears above the mention.
// (The offsetParent should be .Post-body.)
const positionPreview = () => {
const previewHeight = $preview.outerHeight(true);
let offset = 0;
// If the preview goes off the top of the viewport, reposition it to
// be below the mention.
if ($this.offset().top - previewHeight < $(window).scrollTop() + $('#header').outerHeight()) {
offset += $this.outerHeight(true);
} else {
offset -= previewHeight;
}
$preview.show()
.css('top', $this.offset().top - $parentPost.offset().top + offset)
.css('left', $this.offsetParent().offset().left - $parentPost.offset().left)
.css('max-width', $this.offsetParent().width());
};
const showPost = post => {
const discussion = post.discussion();
m.render($preview[0], [
discussion !== parentPost.discussion()
? <li><span className="PostMention-preview-discussion">{discussion.title()}</span></li>
: '',
<li>{PostPreview.component({post})}</li>
]);
positionPreview();
};
const post = app.store.getById('posts', id);
if (post && post.discussion()) {
showPost(post);
} else {
m.render($preview[0], LoadingIndicator.component());
app.store.find('posts', id).then(showPost);
positionPreview();
}
setTimeout(() => $preview.off('transitionend').addClass('in'));
}
};
const hidePreview = () => {
getPostElement().removeClass('pulsate');
if ($preview.hasClass('in')) {
$preview.removeClass('in').one('transitionend', () => $preview.hide());
}
};
$this.on('touchstart', e => e.preventDefault());
$this.add($preview).hover(
() => {
clearTimeout(timeout);
timeout = setTimeout(showPreview, 250);
},
() => {
clearTimeout(timeout);
getPostElement().removeClass('pulsate');
timeout = setTimeout(hidePreview, 250);
}
)
.on('touchend', e => {
showPreview();
e.stopPropagation();
});
$(document).on('touchend', hidePreview);
});
});
}

View File

@@ -0,0 +1,45 @@
import { extend } from 'flarum/extend';
import CommentPost from 'flarum/components/CommentPost';
import PostQuoteButton from './components/PostQuoteButton';
import selectedText from './utils/selectedText';
export default function addPostQuoteButton() {
extend(CommentPost.prototype, 'config', function(original, isInitialized) {
const post = this.props.post;
if (isInitialized || post.isHidden() || (app.session.user && !post.discussion().canReply())) return;
const $postBody = this.$('.Post-body');
// Wrap the quote button in a wrapper element so that we can render
// button into it.
const $container = $('<div class="Post-quoteButtonContainer"></div>');
const handler = function(e) {
setTimeout(() => {
const content = selectedText($postBody);
if (content) {
const button = new PostQuoteButton({post, content});
m.render($container[0], button.render());
const rects = window.getSelection().getRangeAt(0).getClientRects();
const firstRect = rects[0];
if (e.clientY < firstRect.bottom && e.clientX - firstRect.right < firstRect.left - e.clientX) {
button.showStart(firstRect.left, firstRect.top);
} else {
const lastRect = rects[rects.length - 1];
button.showEnd(lastRect.right, lastRect.bottom);
}
}
}, 1);
};
this.$().after($container).on('mouseup', handler);
if ('ontouchstart' in window) {
document.addEventListener('selectionchange', handler, false);
}
});
}

View File

@@ -0,0 +1,22 @@
import { extend } from 'flarum/extend';
import Button from 'flarum/components/Button';
import CommentPost from 'flarum/components/CommentPost';
import reply from './utils/reply';
export default function () {
extend(CommentPost.prototype, 'actionItems', function (items) {
const post = this.props.post;
if (post.isHidden() || (app.session.user && !post.discussion().canReply())) return;
items.add('reply',
Button.component({
className: 'Button Button--link',
children: app.translator.trans('flarum-mentions.forum.post.reply_link'),
onclick: () => reply(post)
})
);
});
}

View File

@@ -0,0 +1,78 @@
import Component from 'flarum/Component';
export default class AutocompleteDropdown extends Component {
init() {
this.active = false;
this.index = 0;
this.keyWasJustPressed = false;
}
view() {
return (
<ul className="Dropdown-menu MentionsDropdown">
{this.props.items.map(item => <li>{item}</li>)}
</ul>
);
}
show(left, top) {
this.$().show().css({
left: left + 'px',
top: top + 'px'
});
this.active = true;
}
hide() {
this.$().hide();
this.active = false;
}
navigate(delta) {
this.keyWasJustPressed = true;
this.setIndex(this.index + delta, true);
clearTimeout(this.keyWasJustPressedTimeout);
this.keyWasJustPressedTimeout = setTimeout(() => this.keyWasJustPressed = false, 500);
}
complete() {
this.$('li').eq(this.index).find('button').click();
}
setIndex(index, scrollToItem) {
if (this.keyWasJustPressed && !scrollToItem) return;
const $dropdown = this.$();
const $items = $dropdown.find('li');
let rangedIndex = index;
if (rangedIndex < 0) {
rangedIndex = $items.length - 1;
} else if (rangedIndex >= $items.length) {
rangedIndex = 0;
}
this.index = rangedIndex;
const $item = $items.removeClass('active').eq(rangedIndex).addClass('active');
if (scrollToItem) {
const dropdownScroll = $dropdown.scrollTop();
const dropdownTop = $dropdown.offset().top;
const dropdownBottom = dropdownTop + $dropdown.outerHeight();
const itemTop = $item.offset().top;
const itemBottom = itemTop + $item.outerHeight();
let scrollTop;
if (itemTop < dropdownTop) {
scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt($dropdown.css('padding-top'), 10);
} else if (itemBottom > dropdownBottom) {
scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt($dropdown.css('padding-bottom'), 10);
}
if (typeof scrollTop !== 'undefined') {
$dropdown.stop(true).animate({scrollTop}, 100);
}
}
}
}

View File

@@ -0,0 +1,24 @@
import PostsUserPage from 'flarum/components/PostsUserPage';
/**
* The `MentionsUserPage` component shows post which user Mentioned at
*/
export default class MentionsUserPage extends PostsUserPage {
/**
* Load a new page of the user's activity feed.
*
* @param {Integer} [offset] The position to start getting results from.
* @return {Promise}
* @protected
*/
loadResults(offset) {
return app.store.find('posts', {
filter: {
type: 'comment',
mentioned: this.user.id()
},
page: {offset, limit: this.loadLimit},
sort: '-time'
});
}
}

View File

@@ -0,0 +1,36 @@
import Notification from 'flarum/components/Notification';
import username from 'flarum/helpers/username';
import punctuateSeries from 'flarum/helpers/punctuateSeries';
export default class PostMentionedNotification extends Notification {
icon() {
return 'fas fa-reply';
}
href() {
const notification = this.props.notification;
const post = notification.subject();
const auc = notification.additionalUnreadCount();
const content = notification.content();
return app.route.discussion(post.discussion(), auc ? post.number() : (content && content.replyNumber));
}
content() {
const notification = this.props.notification;
const auc = notification.additionalUnreadCount();
const user = notification.sender();
return app.translator.transChoice('flarum-mentions.forum.notifications.post_mentioned_text', auc + 1, {
user,
username: auc ? punctuateSeries([
username(user),
app.translator.transChoice('flarum-mentions.forum.notifications.others_text', auc, {count: auc})
]) : undefined
});
}
excerpt() {
return this.props.notification.subject().contentPlain();
}
}

View File

@@ -0,0 +1,53 @@
import Button from 'flarum/components/Button';
import extract from 'flarum/utils/extract';
import reply from '../utils/reply';
export default class PostQuoteButton extends Button {
view() {
const post = extract(this.props, 'post');
const content = extract(this.props, 'content');
this.props.className = 'Button PostQuoteButton';
this.props.icon = 'fas fa-quote-left';
this.props.children = app.translator.trans('flarum-mentions.forum.post.quote_button');
this.props.onclick = () => {
this.hide();
reply(post, content);
};
this.props.onmousedown = (e) => e.stopPropagation();
return super.view();
}
config(isInitialized) {
if (isInitialized) return;
$(document).on('mousedown', this.hide.bind(this));
}
show(left, top) {
const $this = this.$().show();
const parentOffset = $this.offsetParent().offset();
$this
.css('left', left - parentOffset.left)
.css('top', top - parentOffset.top);
}
showStart(left, top) {
const $this = this.$();
this.show(left, $(window).scrollTop() + top - $this.outerHeight() - 5);
}
showEnd(right, bottom) {
const $this = this.$();
this.show(right - $this.outerWidth(), $(window).scrollTop() + bottom + 5);
}
hide() {
this.$().hide();
}
}

View File

@@ -0,0 +1,23 @@
import Notification from 'flarum/components/Notification';
export default class UserMentionedNotification extends Notification {
icon() {
return 'fas fa-at';
}
href() {
const post = this.props.notification.subject();
return app.route.discussion(post.discussion(), post.number());
}
content() {
const user = this.props.notification.sender();
return app.translator.trans('flarum-mentions.forum.notifications.user_mentioned_text', {user});
}
excerpt() {
return this.props.notification.subject().contentPlain();
}
}

View File

@@ -0,0 +1,72 @@
import { extend } from 'flarum/extend';
import app from 'flarum/app';
import NotificationGrid from 'flarum/components/NotificationGrid';
import { getPlainContent } from 'flarum/utils/string';
import addPostMentionPreviews from './addPostMentionPreviews';
import addMentionedByList from './addMentionedByList';
import addPostReplyAction from './addPostReplyAction';
import addPostQuoteButton from './addPostQuoteButton';
import addComposerAutocomplete from './addComposerAutocomplete';
import PostMentionedNotification from './components/PostMentionedNotification';
import UserMentionedNotification from './components/UserMentionedNotification';
import UserPage from 'flarum/components/UserPage'
import LinkButton from 'flarum/components/LinkButton';
import MentionsUserPage from './components/MentionsUserPage';
app.initializers.add('flarum-mentions', function() {
// For every mention of a post inside a post's content, set up a hover handler
// that shows a preview of the mentioned post.
addPostMentionPreviews();
// In the footer of each post, show information about who has replied (i.e.
// who the post has been mentioned by).
addMentionedByList();
// Add a 'reply' control to the footer of each post. When clicked, it will
// open up the composer and add a post mention to its contents.
addPostReplyAction();
// Show a Quote button when Post text is selected
addPostQuoteButton();
// After typing '@' in the composer, show a dropdown suggesting a bunch of
// posts or users that the user could mention.
addComposerAutocomplete();
app.notificationComponents.postMentioned = PostMentionedNotification;
app.notificationComponents.userMentioned = UserMentionedNotification;
// Add notification preferences.
extend(NotificationGrid.prototype, 'notificationTypes', function(items) {
items.add('postMentioned', {
name: 'postMentioned',
icon: 'fas fa-reply',
label: app.translator.trans('flarum-mentions.forum.settings.notify_post_mentioned_label')
});
items.add('userMentioned', {
name: 'userMentioned',
icon: 'fas fa-at',
label: app.translator.trans('flarum-mentions.forum.settings.notify_user_mentioned_label')
});
});
// Add mentions tab in user profile
app.routes['user.mentions'] = {path: '/u/:username/mentions', component: MentionsUserPage.component()};
extend(UserPage.prototype, 'navItems', function(items) {
const user = this.user;
items.add('mentions',
LinkButton.component({
href: app.route('user.mentions', {username: user.username()}),
name: 'mentions',
children: [app.translator.trans('flarum-mentions.forum.user.mentions_link')],
icon: 'fas fa-at'
}),
80
);
});
// Remove post mentions when rendering post previews.
getPlainContent.removeSelectors.push('a.PostMention');
});

View File

@@ -0,0 +1,34 @@
import DiscussionControls from 'flarum/utils/DiscussionControls';
function insertMention(post, component, quote) {
const user = post.user();
const mention = '@' + (user ? user.username() : post.number()) + '#' + post.id() + ' ';
// 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
// close the composer straight away.
if (!component.content()) {
component.props.originalContent = mention;
}
const cursorPosition = component.editor.getSelectionRange()[0];
const preceding = component.editor.value().slice(0, cursorPosition);
const precedingNewlines = preceding.length == 0 ? 0 : 3 - preceding.match(/(\n{0,2})$/)[0].length;
component.editor.insertAtCursor(
Array(precedingNewlines).join('\n') + // Insert up to two newlines, depending on preceding whitespace
(quote
? '> ' + mention + quote.trim().replace(/\n/g, '\n> ') + '\n\n'
: mention)
);
}
export default function reply(post, quote) {
const component = app.composer.component;
if (component && component.props.post && component.props.post.discussion() === post.discussion()) {
insertMention(post, component, quote);
} else {
DiscussionControls.replyAction.call(post.discussion())
.then(newComponent => insertMention(post, newComponent, quote));
}
}

View File

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

View File

@@ -0,0 +1,25 @@
import username from 'flarum/helpers/username';
import extractText from 'flarum/utils/extractText';
export function filterUserMentions(tag) {
const user = app.store.getBy('users', 'username', tag.getAttribute('username'));
if (user) {
tag.setAttribute('id', user.id());
tag.setAttribute('displayname', extractText(username(user)));
return true;
}
}
export function filterPostMentions(tag) {
const post = app.store.getById('posts', tag.getAttribute('id'));
if (post) {
tag.setAttribute('discussionid', post.discussion().id());
tag.setAttribute('number', post.number());
tag.setAttribute('displayname', extractText(username(post.user())));
return true;
}
}