\n ) : null}\n >\n );\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['forum/components/Notification'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/utils/string'];","import app from 'flarum/forum/app';\nimport Notification from 'flarum/forum/components/Notification';\nimport { truncate } from 'flarum/common/utils/string';\n\nexport default class PostLikedNotification extends Notification {\n icon() {\n return 'far fa-thumbs-up';\n }\n\n href() {\n return app.route.post(this.attrs.notification.subject());\n }\n\n content() {\n const notification = this.attrs.notification;\n const user = notification.fromUser();\n\n return app.translator.trans('flarum-likes.forum.notifications.post_liked_text', { user, count: 1 });\n }\n\n excerpt() {\n return truncate(this.attrs.notification.subject().contentPlain(), 200);\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['forum/components/UserPage'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/components/LinkButton'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/extenders'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/models/Post'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['forum/components/PostsUserPage'];","import app from 'flarum/forum/app';\nimport PostsUserPage from 'flarum/forum/components/PostsUserPage';\n\n/**\n * The `LikesUserPage` component shows posts which user the user liked.\n */\nexport default class LikesUserPage extends PostsUserPage {\n /**\n * Load a new page of the user's activity feed.\n *\n * @param offset The position to start getting results from.\n * @protected\n */\n loadResults(offset: number) {\n return app.store.find('posts', {\n filter: {\n type: 'comment',\n likedBy: this.user.id(),\n },\n page: { offset, limit: this.loadLimit },\n sort: '-createdAt',\n });\n }\n}\n","import Extend from 'flarum/common/extenders';\nimport Post from 'flarum/common/models/Post';\nimport User from 'flarum/common/models/User';\nimport LikesUserPage from './components/LikesUserPage';\n\nexport default [\n new Extend.Routes() //\n .add('user.likes', '/u/:username/likes', LikesUserPage),\n\n new Extend.Model(Post) //\n .hasMany('likes')\n .attribute('likesCount')\n .attribute('canLike'),\n];\n","import { extend } from 'flarum/common/extend';\nimport app from 'flarum/forum/app';\nimport NotificationGrid from 'flarum/forum/components/NotificationGrid';\n\nimport addLikeAction from './addLikeAction';\nimport addLikesList from './addLikesList';\nimport PostLikedNotification from './components/PostLikedNotification';\nimport addLikesTabToUserProfile from './addLikesTabToUserProfile';\n\nexport { default as extend } from './extend';\n\napp.initializers.add('flarum-likes', () => {\n app.notificationComponents.postLiked = PostLikedNotification;\n\n addLikeAction();\n addLikesList();\n addLikesTabToUserProfile();\n\n extend(NotificationGrid.prototype, 'notificationTypes', function (items) {\n items.add('postLiked', {\n name: 'postLiked',\n icon: 'far fa-thumbs-up',\n label: app.translator.trans('flarum-likes.forum.settings.notify_post_liked_label'),\n });\n });\n});\n","import { extend } from 'flarum/common/extend';\nimport app from 'flarum/forum/app';\nimport Button from 'flarum/common/components/Button';\nimport CommentPost from 'flarum/forum/components/CommentPost';\n\nexport default function () {\n extend(CommentPost.prototype, 'actionItems', function (items) {\n const post = this.attrs.post;\n\n if (post.isHidden() || !post.canLike()) return;\n\n const likes = post.likes();\n\n let isLiked = app.session.user && likes && likes.some((user) => user === app.session.user);\n\n items.add(\n 'like',\n \n );\n });\n}\n","import { extend } from 'flarum/common/extend';\nimport app from 'flarum/forum/app';\nimport CommentPost from 'flarum/forum/components/CommentPost';\nimport Link from 'flarum/common/components/Link';\nimport punctuateSeries from 'flarum/common/helpers/punctuateSeries';\nimport username from 'flarum/common/helpers/username';\nimport icon from 'flarum/common/helpers/icon';\nimport Button from 'flarum/common/components/Button';\n\nimport PostLikesModal from './components/PostLikesModal';\n\nexport default function () {\n extend(CommentPost.prototype, 'footerItems', function (items) {\n const post = this.attrs.post;\n const likes = post.likes();\n\n if (likes && likes.length) {\n const limit = 4;\n const overLimit = post.likesCount() > limit;\n\n // Construct a list of names of users who have liked this post. Make sure the\n // current user is first in the list, and cap a maximum of 4 items.\n const names = likes\n .sort((a) => (a === app.session.user ? -1 : 1))\n .slice(0, overLimit ? limit - 1 : limit)\n .map((user) => {\n return (\n \n {user === app.session.user ? app.translator.trans('flarum-likes.forum.post.you_text') : username(user)}\n \n );\n });\n\n // If there are more users that we've run out of room to display, add a \"x\n // others\" name to the end of the list. Clicking on it will display a modal\n // with a full list of names.\n if (overLimit) {\n const count = post.likesCount() - names.length;\n const label = app.translator.trans('flarum-likes.forum.post.others_link', { count });\n\n if (app.forum.attribute('canSearchUsers')) {\n names.push(\n \n );\n } else {\n names.push({label});\n }\n }\n\n items.add(\n 'liked',\n
\n )}\n >\n );\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['forum/utils/DiscussionControls'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['forum/components/EditPostComposer'];","import app from 'flarum/forum/app';\nimport DiscussionControls from 'flarum/forum/utils/DiscussionControls';\nimport EditPostComposer from 'flarum/forum/components/EditPostComposer';\n\nexport function insertMention(post, composer, quote) {\n return new Promise((resolve) => {\n const mention = app.mentionFormats.mentionable('post').replacement(post) + ' ';\n\n // If the composer is empty, then assume we're starting a new reply.\n // In which case we don't want the user to have to confirm if they\n // close the composer straight away.\n if (!composer.fields.content()) {\n composer.body.attrs.originalContent = mention;\n }\n\n const cursorPosition = composer.editor.getSelectionRange()[0];\n const preceding = composer.fields.content().slice(0, cursorPosition);\n const precedingNewlines = preceding.length == 0 ? 0 : 3 - preceding.match(/(\\n{0,2})$/)[0].length;\n\n composer.editor.insertAtCursor(\n Array(precedingNewlines).join('\\n') + // Insert up to two newlines, depending on preceding whitespace\n (quote ? '> ' + mention + quote.trim().replace(/\\n/g, '\\n> ') + '\\n\\n' : mention),\n false\n );\n return resolve(composer);\n });\n}\n\nexport default function reply(post, quote) {\n if (app.composer.bodyMatches(EditPostComposer) && app.composer.body.attrs.post.discussion() === post.discussion()) {\n // If we're already editing a post in the discussion of post we're quoting,\n // insert the mention directly.\n return insertMention(post, app.composer, quote);\n } else {\n // The default \"Reply\" action behavior will only open a new composer if\n // necessary, but it will always be a ReplyComposer, hence the exceptional\n // case above.\n return DiscussionControls.replyAction.call(post.discussion()).then((composer) => insertMention(post, composer, quote));\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/Fragment'];","import app from 'flarum/forum/app';\nimport Fragment from 'flarum/common/Fragment';\nimport icon from 'flarum/common/helpers/icon';\n\nimport reply from '../utils/reply';\n\nexport default class PostQuoteButton extends Fragment {\n constructor(post) {\n super();\n\n this.post = post;\n }\n\n view() {\n return (\n \n );\n }\n\n show(left, top) {\n const $this = this.$().show();\n const parentOffset = $this.offsetParent().offset();\n\n $this.css('left', left - parentOffset.left).css('top', top - parentOffset.top);\n\n this.hideHandler = this.hide.bind(this);\n $(document).on('mouseup', this.hideHandler);\n }\n\n showStart(left, top) {\n const $this = this.$();\n\n this.show(left, $(window).scrollTop() + top - $this.outerHeight() - 5);\n }\n\n showEnd(right, bottom) {\n const $this = this.$();\n\n this.show(right - $this.outerWidth(), $(window).scrollTop() + bottom + 5);\n }\n\n hide() {\n this.$().hide();\n $(document).off('mouseup', this.hideHandler);\n }\n}\n","/**\n * Finds the selected text in the provided composer body.\n */\nexport default function selectedText(body) {\n const selection = window.getSelection();\n\n if (!selection.isCollapsed) {\n const range = selection.getRangeAt(0);\n const parent = range.commonAncestorContainer;\n\n if (body[0] === parent || $.contains(body[0], parent)) {\n const clone = $('
').append(range.cloneContents());\n\n // Replace emoji images with their shortcode (found in alt attribute)\n clone.find('img.emoji').replaceWith(function () {\n return this.alt;\n });\n\n // Replace all other images with a Markdown image\n clone.find('img').replaceWith(function () {\n return `![](${this.src})`;\n });\n\n // Replace all links with a Markdown link\n clone.find('a').replaceWith(function () {\n return `[${this.innerText}](${this.href})`;\n });\n\n return clone.text();\n }\n }\n return '';\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/components/TextEditor'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/components/TextEditorButton'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/utils/KeyboardNavigatable'];","export default function _typeof(obj) {\n \"@babel/helpers - typeof\";\n\n return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (obj) {\n return typeof obj;\n } : function (obj) {\n return obj && \"function\" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj;\n }, _typeof(obj);\n}","import toPropertyKey from \"./toPropertyKey.js\";\nexport default function _defineProperty(obj, key, value) {\n key = toPropertyKey(key);\n if (key in obj) {\n Object.defineProperty(obj, key, {\n value: value,\n enumerable: true,\n configurable: true,\n writable: true\n });\n } else {\n obj[key] = value;\n }\n return obj;\n}","import _typeof from \"./typeof.js\";\nimport toPrimitive from \"./toPrimitive.js\";\nexport default function _toPropertyKey(arg) {\n var key = toPrimitive(arg, \"string\");\n return _typeof(key) === \"symbol\" ? key : String(key);\n}","import _typeof from \"./typeof.js\";\nexport default function _toPrimitive(input, hint) {\n if (_typeof(input) !== \"object\" || input === null) return input;\n var prim = input[Symbol.toPrimitive];\n if (prim !== undefined) {\n var res = prim.call(input, hint || \"default\");\n if (_typeof(res) !== \"object\") return res;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (hint === \"string\" ? String : Number)(input);\n}","import Fragment from 'flarum/common/Fragment';\n\nexport default class AutocompleteDropdown extends Fragment {\n items = [];\n active = false;\n index = 0;\n keyWasJustPressed = false;\n\n view() {\n return (\n
\n {this.items.map((item) => (\n
{item}
\n ))}\n
\n );\n }\n\n show(left, top) {\n this.$()\n .show()\n .css({\n left: left + 'px',\n top: top + 'px',\n });\n this.active = true;\n }\n\n hide() {\n this.$().hide();\n this.active = false;\n }\n\n navigate(delta) {\n this.keyWasJustPressed = true;\n this.setIndex(this.index + delta, true);\n clearTimeout(this.keyWasJustPressedTimeout);\n this.keyWasJustPressedTimeout = setTimeout(() => (this.keyWasJustPressed = false), 500);\n }\n\n complete() {\n this.$('li').eq(this.index).find('button').click();\n }\n\n setIndex(index, scrollToItem) {\n if (this.keyWasJustPressed && !scrollToItem) return;\n\n const $dropdown = this.$();\n const $items = $dropdown.find('li');\n let rangedIndex = index;\n\n if (rangedIndex < 0) {\n rangedIndex = $items.length - 1;\n } else if (rangedIndex >= $items.length) {\n rangedIndex = 0;\n }\n\n this.index = rangedIndex;\n\n const $item = $items.removeClass('active').eq(rangedIndex).addClass('active');\n\n if (scrollToItem) {\n const dropdownScroll = $dropdown.scrollTop();\n const dropdownTop = $dropdown.offset().top;\n const dropdownBottom = dropdownTop + $dropdown.outerHeight();\n const itemTop = $item.offset().top;\n const itemBottom = itemTop + $item.outerHeight();\n\n let scrollTop;\n if (itemTop < dropdownTop) {\n scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt($dropdown.css('padding-top'), 10);\n } else if (itemBottom > dropdownBottom) {\n scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt($dropdown.css('padding-bottom'), 10);\n }\n\n if (typeof scrollTop !== 'undefined') {\n $dropdown.stop(true).animate({ scrollTop }, 100);\n }\n }\n }\n}\n","import type MentionableModel from '../MentionableModel';\nimport type Model from 'flarum/common/Model';\n\nexport default abstract class MentionFormat {\n protected instances?: MentionableModel[];\n\n public makeMentionables(): MentionableModel[] {\n return this.instances ?? (this.instances = this.mentionables.map((Mentionable) => new Mentionable(this)));\n }\n\n public getMentionable(type: string): MentionableModel | null {\n return this.makeMentionables().find((mentionable) => mentionable.type() === type) ?? null;\n }\n\n public extend(mentionable: new (...args: any[]) => MentionableModel): void {\n if (!this.extendable) throw new Error('This mention format does not allow extending.');\n\n this.mentionables.push(mentionable);\n }\n\n abstract mentionables: (new (...args: any[]) => MentionableModel)[];\n\n protected abstract extendable: boolean;\n\n abstract trigger(): string;\n\n /**\n * Picks the term to search in the API from the typed text.\n * @example:\n * * Full text = `Hello @\"John D`\n * * Typed text = `\"John D`\n * * Query = `John D`\n */\n abstract queryFromTyped(typed: string): string | null;\n\n abstract format(...args: any): string;\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/helpers/avatar'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/helpers/highlight'];","import type Mithril from 'mithril';\nimport type Model from 'flarum/common/Model';\nimport type MentionFormat from './formats/MentionFormat';\n\nexport default abstract class MentionableModel {\n public format: Format;\n\n public constructor(format: Format) {\n this.format = format;\n }\n\n abstract type(): string;\n abstract initialResults(): M[];\n abstract search(typed: string): Promise;\n abstract replacement(model: M): string;\n abstract suggestion(model: M, typed: string): Mithril.Children;\n abstract matches(model: M, typed: string): boolean;\n abstract maxStoreMatchedResults(): number | null;\n abstract enabled(): boolean;\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/utils/extractText'];","import app from 'flarum/forum/app';\nimport extractText from 'flarum/common/utils/extractText';\n\n/**\n * Whether to use the old mentions format.\n *\n * `'@username'` or `'@\"Display name\"'`\n */\nexport const shouldUseOldFormat = () => app.forum.attribute('allowUsernameMentionFormat') || false;\n\nconst getDeletedUserText = () => extractText(app.translator.trans('core.lib.username.deleted_text'));\n\n/**\n * Fetches a user's username or display name.\n *\n * Chooses based on the format option set in the admin settings page.\n *\n * @param user An instance of the User model to fetch the username for\n * @param useDisplayName If `true`, uses `user.displayName()`, otherwise, uses `user.username()`\n */\nexport default function getCleanDisplayName(user, useDisplayName = true) {\n if (!user) return getDeletedUserText().replace(/\"#[a-z]{0,3}[0-9]+/, '_');\n\n const text = (useDisplayName ? user.displayName() : user.username()) || getDeletedUserText();\n\n return text.replace(/\"#[a-z]{0,3}[0-9]+/, '_');\n}\n","import app from 'flarum/forum/app';\nimport type Mithril from 'mithril';\nimport type User from 'flarum/common/models/User';\nimport usernameHelper from 'flarum/common/helpers/username';\nimport avatar from 'flarum/common/helpers/avatar';\nimport highlight from 'flarum/common/helpers/highlight';\nimport MentionableModel from './MentionableModel';\nimport getCleanDisplayName, { shouldUseOldFormat } from '../utils/getCleanDisplayName';\nimport AtMentionFormat from './formats/AtMentionFormat';\n\nexport default class UserMention extends MentionableModel {\n type(): string {\n return 'user';\n }\n\n initialResults(): User[] {\n return Array.from(app.store.all('users'));\n }\n\n /**\n * Automatically determines which mention syntax to be used based on the option in the\n * admin dashboard. Also performs display name clean-up automatically.\n *\n * @\"Display name\"#UserID or `@username`\n *\n * @example
New display name syntax
\n * // '@\"user\"#1'\n * forUser(User) // User is ID 1, display name is 'User'\n *\n * @example
Using old syntax
\n * // '@username'\n * forUser(user) // User's username is 'username'\n */\n public replacement(user: User): string {\n if (shouldUseOldFormat()) {\n const cleanText = getCleanDisplayName(user, false);\n return this.format.format(cleanText);\n }\n\n const cleanText = getCleanDisplayName(user);\n return this.format.format(cleanText, '', user.id());\n }\n\n suggestion(model: User, typed: string): Mithril.Children {\n const username = usernameHelper(model, (name: string) => highlight(name, typed));\n\n return (\n <>\n {avatar(model)}\n {username}\n >\n );\n }\n\n matches(model: User, typed: string): boolean {\n if (!typed) return false;\n\n const names = [model.username(), model.displayName()];\n\n return names.some((name) => name.toLowerCase().substr(0, typed.length) === typed);\n }\n\n maxStoreMatchedResults(): null {\n return null;\n }\n\n async search(typed: string): Promise {\n return await app.store.find('users', { filter: { q: typed }, page: { limit: 5 } });\n }\n\n enabled(): boolean {\n return true;\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['forum/components/ReplyComposer'];","import app from 'flarum/forum/app';\nimport MentionableModel from './MentionableModel';\nimport type Post from 'flarum/common/models/Post';\nimport type Mithril from 'mithril';\nimport usernameHelper from 'flarum/common/helpers/username';\nimport avatar from 'flarum/common/helpers/avatar';\nimport highlight from 'flarum/common/helpers/highlight';\nimport { truncate } from 'flarum/common/utils/string';\nimport ReplyComposer from 'flarum/forum/components/ReplyComposer';\nimport EditPostComposer from 'flarum/forum/components/EditPostComposer';\nimport getCleanDisplayName from '../utils/getCleanDisplayName';\nimport type AtMentionFormat from './formats/AtMentionFormat';\n\nexport default class PostMention extends MentionableModel {\n type(): string {\n return 'post';\n }\n\n /**\n * If the user is replying to a discussion, or if they are editing a\n * post, then we can suggest other posts in the discussion to mention.\n * We will add the 5 most recent comments in the discussion which\n * match any username characters that have been typed.\n */\n initialResults(): Post[] {\n if (!app.composer.bodyMatches(ReplyComposer) && !app.composer.bodyMatches(EditPostComposer)) {\n return [];\n }\n\n // @ts-ignore\n const composerAttrs = app.composer.body.attrs;\n const composerPost = composerAttrs.post;\n const discussion = (composerPost && composerPost.discussion()) || composerAttrs.discussion;\n\n return (\n discussion\n .posts()\n // Filter to only comment posts, and replies before this message\n .filter((post: Post) => post && post.contentType() === 'comment' && (!composerPost || post.number() < composerPost.number()))\n // Sort by new to old\n .sort((a: Post, b: Post) => b.createdAt().getTime() - a.createdAt().getTime())\n );\n }\n\n /**\n * Generates the syntax for mentioning of a post. Also cleans up the display name.\n *\n * @example
Post mention
\n * // '@\"User\"#p13'\n * // @\"Display name\"#pPostID\n * forPostMention(user, 13) // User display name is 'User', post ID is 13\n */\n public replacement(post: Post): string {\n const user = post.user();\n const cleanText = getCleanDisplayName(user);\n return this.format.format(cleanText, 'p', post.id());\n }\n\n suggestion(model: Post, typed: string): Mithril.Children {\n const user = model.user() || null;\n const username = usernameHelper(user, (name: string) => highlight(name, typed));\n\n return (\n <>\n {avatar(user)}\n {username}\n {[\n app.translator.trans('flarum-mentions.forum.composer.reply_to_post_text', { number: model.number() }),\n ' — ',\n truncate(model.contentPlain() ?? '', 200),\n ]}\n >\n );\n }\n\n matches(model: Post, typed: string): boolean {\n const user = model.user();\n const userMentionable = app.mentionFormats.mentionable('user')!;\n\n return !typed || (user && userMentionable.matches(user, typed));\n }\n\n maxStoreMatchedResults(): number {\n return 5;\n }\n\n /**\n * Post mention suggestions are only offered from current discussion posts.\n */\n search(typed: string): Promise {\n return Promise.resolve([]);\n }\n\n enabled(): boolean {\n return true;\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/models/Group'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/components/Badge'];","import app from 'flarum/forum/app';\nimport Group from 'flarum/common/models/Group';\nimport MentionableModel from './MentionableModel';\nimport type Mithril from 'mithril';\nimport Badge from 'flarum/common/components/Badge';\nimport highlight from 'flarum/common/helpers/highlight';\nimport type AtMentionFormat from './formats/AtMentionFormat';\n\nexport default class GroupMention extends MentionableModel {\n type(): string {\n return 'group';\n }\n\n initialResults(): Group[] {\n return Array.from(\n app.store.all('groups').filter((g: Group) => {\n return g.id() !== Group.GUEST_ID && g.id() !== Group.MEMBER_ID;\n })\n );\n }\n\n /**\n * Generates the mention syntax for a group mention.\n *\n * @\"Name Plural\"#gGroupID\n *\n * @example
Group mention
\n * // '@\"Mods\"#g4'\n * forGroup(group) // Group display name is 'Mods', group ID is 4\n */\n public replacement(group: Group): string {\n return this.format.format(group.namePlural(), 'g', group.id());\n }\n\n suggestion(model: Group, typed: string): Mithril.Children {\n let groupName: Mithril.Children = model.namePlural();\n\n if (typed) {\n groupName = highlight(groupName, typed);\n }\n\n return (\n <>\n \n {groupName}\n >\n );\n }\n\n matches(model: Group, typed: string): boolean {\n if (!typed) return false;\n\n const names = [model.namePlural().toLowerCase(), model.nameSingular().toLowerCase()];\n\n return names.some((name) => name.toLowerCase().substr(0, typed.length) === typed);\n }\n\n maxStoreMatchedResults(): null {\n return null;\n }\n\n /**\n * All groups are already loaded, so we don't need to search for them.\n */\n search(typed: string): Promise {\n return Promise.resolve([]);\n }\n\n enabled(): boolean {\n return app.session?.user?.canMentionGroups() ?? false;\n }\n}\n","import MentionFormat from './MentionFormat';\nimport type MentionableModel from '../MentionableModel';\nimport UserMention from '../UserMention';\nimport PostMention from '../PostMention';\nimport GroupMention from '../GroupMention';\n\nexport default class AtMentionFormat extends MentionFormat {\n public mentionables: (new (...args: any[]) => MentionableModel)[] = [UserMention, PostMention, GroupMention];\n protected extendable: boolean = true;\n\n public trigger(): string {\n return '@';\n }\n\n public queryFromTyped(typed: string): string | null {\n const matchTyped = typed.match(/^[\"“]?((?:(?!\"#).)+)$/);\n\n return matchTyped ? matchTyped[1] : null;\n }\n\n public format(name: string, char: string | null = '', id: string | null = null): string {\n return {\n simple: `@${name}`,\n safe: `@\"${name}\"#${char}${id}`,\n }[id ? 'safe' : 'simple'];\n }\n}\n","import app from 'flarum/forum/app';\nimport Badge from 'flarum/common/components/Badge';\nimport highlight from 'flarum/common/helpers/highlight';\nimport type Tag from 'flarum/tags/common/models/Tag';\nimport type Mithril from 'mithril';\nimport MentionableModel from './MentionableModel';\nimport type HashMentionFormat from './formats/HashMentionFormat';\n\nexport default class TagMention extends MentionableModel {\n type(): string {\n return 'tag';\n }\n\n initialResults(): Tag[] {\n return Array.from(app.store.all('tags'));\n }\n\n /**\n * Generates the mention syntax for a tag mention.\n *\n * ~tagSlug\n *\n * @example
Tag mention
\n * // ~general\n * forTag(tag) // Tag display name is 'Tag', tag ID is 5\n */\n public replacement(tag: Tag): string {\n return this.format.format(tag.slug());\n }\n\n matches(model: Tag, typed: string): boolean {\n if (!typed) return false;\n\n const names = [model.name().toLowerCase()];\n\n return names.some((name) => name.toLowerCase().substr(0, typed.length) === typed);\n }\n\n maxStoreMatchedResults(): null {\n return null;\n }\n\n async search(typed: string): Promise {\n return await app.store.find('tags', { filter: { q: typed }, page: { limit: 5 } });\n }\n\n suggestion(model: Tag, typed: string): Mithril.Children {\n let tagName: Mithril.Children = model.name();\n\n if (typed) {\n tagName = highlight(tagName, typed);\n }\n\n return (\n <>\n \n {tagName}\n >\n );\n }\n\n enabled(): boolean {\n return 'flarum-tags' in flarum.extensions;\n }\n}\n","import MentionFormat from './MentionFormat';\nimport MentionableModel from '../MentionableModel';\nimport TagMention from '../TagMention';\n\nexport default class HashMentionFormat extends MentionFormat {\n public mentionables: (new (...args: any[]) => MentionableModel)[] = [TagMention];\n protected extendable: boolean = false;\n\n public trigger(): string {\n return '#';\n }\n\n public queryFromTyped(typed: string): string | null {\n const matchTyped = typed.match(/^[-_\\p{L}\\p{N}\\p{M}]+$/giu);\n\n return matchTyped ? matchTyped[0] : null;\n }\n\n public format(slug: string): string {\n return `#${slug}`;\n }\n}\n","import AtMentionFormat from './AtMentionFormat';\nimport HashMentionFormat from './HashMentionFormat';\nimport type MentionFormat from './MentionFormat';\nimport MentionableModel from '../MentionableModel';\n\nexport default class MentionFormats {\n protected formats: MentionFormat[] = [new AtMentionFormat(), new HashMentionFormat()];\n\n public get(symbol: string): MentionFormat | null {\n return this.formats.find((f) => f.trigger() === symbol) ?? null;\n }\n\n public mentionable(type: string): MentionableModel | null {\n for (const format of this.formats) {\n const mentionable = format.getMentionable(type);\n\n if (mentionable) return mentionable;\n }\n\n return null;\n }\n\n public extend(format: new () => MentionFormat) {\n this.formats.push(new format());\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/Component'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/utils/classList'];","import Component from 'flarum/common/Component';\nimport type { ComponentAttrs } from 'flarum/common/Component';\nimport classList from 'flarum/common/utils/classList';\nimport type MentionableModel from '../mentionables/MentionableModel';\nimport type Mithril from 'mithril';\n\nexport interface IMentionsDropdownItemAttrs extends ComponentAttrs {\n mentionable: MentionableModel;\n onclick: () => void;\n onmouseenter: () => void;\n}\n\nexport default class MentionsDropdownItem extends Component {\n view(vnode: Mithril.Vnode): Mithril.Children {\n const { mentionable, ...attrs } = this.attrs;\n\n const className = classList('MentionsDropdownItem', 'PostPreview', `MentionsDropdown-${mentionable.type()}`);\n\n return (\n \n );\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/utils/throttleDebounce'];","import type MentionableModel from './MentionableModel';\nimport type Model from 'flarum/common/Model';\nimport type Mithril from 'mithril';\nimport MentionsDropdownItem from '../components/MentionsDropdownItem';\nimport { throttle } from 'flarum/common/utils/throttleDebounce';\n\nexport default class MentionableModels {\n protected mentionables?: MentionableModel[];\n /**\n * We store models returned from an API here to preserve order in which they are returned\n * This prevents the list jumping around while models are returned.\n * We also use a hashmap for model IDs to provide O(1) lookup for the users already in the list.\n */\n private results: Record> = {};\n public typed: string | null = null;\n private searched: string[] = [];\n private dropdownItemAttrs: Record = {};\n\n constructor(dropdownItemAttrs: Record) {\n this.dropdownItemAttrs = dropdownItemAttrs;\n }\n\n public init(mentionables: MentionableModel[]): void {\n this.typed = null;\n this.mentionables = mentionables;\n\n for (const mentionable of this.mentionables) {\n this.results[mentionable.type()] = new Map(mentionable.initialResults().map((result) => [result.id() as string, result]));\n }\n }\n\n /**\n * Don't send API calls searching for models until at least 2 characters have been typed.\n * This focuses the mention results on models already loaded.\n */\n public readonly search = throttle(250, async (): Promise => {\n if (!this.typed || this.typed.length <= 1) return;\n\n const typedLower = this.typed.toLowerCase();\n\n if (this.searched.includes(typedLower)) return;\n\n for (const mentionable of this.mentionables!) {\n for (const model of await mentionable.search(typedLower)) {\n if (!this.results[mentionable.type()].has(model.id() as string)) {\n this.results[mentionable.type()].set(model.id() as string, model);\n }\n }\n }\n\n this.searched.push(typedLower);\n\n return Promise.resolve();\n });\n\n public matches(mentionable: MentionableModel, model: Model): boolean {\n return mentionable.matches(model, this.typed?.toLowerCase() || '');\n }\n\n public makeSuggestion(mentionable: MentionableModel, model: Model): Mithril.Children {\n const content = mentionable.suggestion(model, this.typed!);\n const replacement = mentionable.replacement(model);\n\n const { onclick, ...attrs } = this.dropdownItemAttrs;\n\n return (\n onclick(replacement)} {...attrs}>\n {content}\n \n );\n }\n\n public buildSuggestions(): Mithril.Children {\n const suggestions: Mithril.Children = [];\n\n for (const mentionable of this.mentionables!) {\n if (!mentionable.enabled()) continue;\n\n let matches = Array.from(this.results[mentionable.type()].values()).filter((model) => this.matches(mentionable, model));\n\n const max = mentionable.maxStoreMatchedResults();\n if (max) matches = matches.splice(0, max);\n\n for (const model of matches) {\n const dropdownItem = this.makeSuggestion(mentionable, model);\n suggestions.push(dropdownItem);\n }\n }\n\n return suggestions;\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['forum/components/Notification'];","import app from 'flarum/forum/app';\nimport Notification from 'flarum/forum/components/Notification';\nimport { truncate } from 'flarum/common/utils/string';\n\nexport default class PostMentionedNotification extends Notification {\n icon() {\n return 'fas fa-reply';\n }\n\n href() {\n const notification = this.attrs.notification;\n const post = notification.subject();\n const content = notification.content();\n\n return app.route.discussion(post.discussion(), content && content.replyNumber);\n }\n\n content() {\n const notification = this.attrs.notification;\n const user = notification.fromUser();\n\n return app.translator.trans('flarum-mentions.forum.notifications.post_mentioned_text', { user, count: 1 });\n }\n\n excerpt() {\n return truncate(this.attrs.notification.subject().contentPlain() || '', 200);\n }\n}\n","import app from 'flarum/forum/app';\nimport Notification from 'flarum/forum/components/Notification';\nimport { truncate } from 'flarum/common/utils/string';\n\nexport default class UserMentionedNotification extends Notification {\n icon() {\n return 'fas fa-at';\n }\n\n href() {\n const post = this.attrs.notification.subject();\n\n return app.route.discussion(post.discussion(), post.number());\n }\n\n content() {\n const user = this.attrs.notification.fromUser();\n\n return app.translator.trans('flarum-mentions.forum.notifications.user_mentioned_text', { user });\n }\n\n excerpt() {\n return truncate(this.attrs.notification.subject().contentPlain(), 200);\n }\n}\n","import app from 'flarum/forum/app';\nimport Notification from 'flarum/forum/components/Notification';\nimport { truncate } from 'flarum/common/utils/string';\n\nexport default class GroupMentionedNotification extends Notification {\n icon() {\n return 'fas fa-at';\n }\n\n href() {\n const post = this.attrs.notification.subject();\n\n return app.route.discussion(post.discussion(), post.number());\n }\n\n content() {\n const user = this.attrs.notification.fromUser();\n\n return app.translator.trans('flarum-mentions.forum.notifications.group_mentioned_text', { user });\n }\n\n excerpt() {\n return truncate(this.attrs.notification.subject().contentPlain(), 200);\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['forum/components/UserPage'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/components/LinkButton'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/models/User'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/Model'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/extenders'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/models/Post'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['forum/components/PostsUserPage'];","import app from 'flarum/forum/app';\nimport PostsUserPage from 'flarum/forum/components/PostsUserPage';\n\n/**\n * The `MentionsUserPage` component shows post which user Mentioned at\n */\nexport default class MentionsUserPage extends PostsUserPage {\n /**\n * Load a new page of the user's activity feed.\n *\n * @param {Integer} [offset] The position to start getting results from.\n * @return {Promise}\n * @protected\n */\n loadResults(offset) {\n return app.store.find('posts', {\n filter: {\n type: 'comment',\n mentioned: this.user.id(),\n },\n page: { offset, limit: this.loadLimit },\n sort: '-createdAt',\n });\n }\n}\n","import Extend from 'flarum/common/extenders';\nimport Post from 'flarum/common/models/Post';\nimport User from 'flarum/common/models/User';\nimport MentionsUserPage from './components/MentionsUserPage';\n\nexport default [\n new Extend.Routes() //\n .add('user.mentions', '/u/:username/mentions', MentionsUserPage),\n\n new Extend.Model(Post) //\n .hasMany('mentionedBy')\n .attribute('mentionedByCount'),\n\n new Extend.Model(User) //\n .attribute('canMentionGroups'),\n];\n","import app from 'flarum/forum/app';\nimport username from 'flarum/common/helpers/username';\nimport extractText from 'flarum/common/utils/extractText';\n\nexport function filterUserMentions(tag) {\n let user;\n\n if (app.forum.attribute('allowUsernameMentionFormat') && tag.hasAttribute('username'))\n user = app.store.getBy('users', 'username', tag.getAttribute('username'));\n else if (tag.hasAttribute('id')) user = app.store.getById('users', tag.getAttribute('id'));\n\n if (user) {\n tag.setAttribute('id', user.id());\n tag.setAttribute('slug', user.slug());\n tag.setAttribute('displayname', extractText(username(user)));\n\n return true;\n }\n\n tag.invalidate();\n}\n\nexport function postFilterUserMentions(tag) {\n tag.setAttribute('deleted', false);\n}\n\nexport function filterPostMentions(tag) {\n const post = app.store.getById('posts', tag.getAttribute('id'));\n\n if (post) {\n tag.setAttribute('discussionid', post.discussion().id());\n tag.setAttribute('number', post.number());\n tag.setAttribute('displayname', extractText(username(post.user())));\n\n return true;\n }\n}\n\nexport function postFilterPostMentions(tag) {\n tag.setAttribute('deleted', false);\n}\n\nexport function filterGroupMentions(tag) {\n if (app.session?.user?.canMentionGroups()) {\n const group = app.store.getById('groups', tag.getAttribute('id'));\n\n if (group) {\n tag.setAttribute('groupname', extractText(group.namePlural()));\n\n return true;\n }\n }\n\n tag.invalidate();\n}\n\nexport function postFilterGroupMentions(tag) {\n if (app.session?.user?.canMentionGroups()) {\n const group = app.store.getById('groups', tag.getAttribute('id'));\n\n tag.setAttribute('color', group.color());\n tag.setAttribute('icon', group.icon());\n tag.setAttribute('deleted', false);\n }\n}\n\nexport function filterTagMentions(tag) {\n if ('flarum-tags' in flarum.extensions) {\n const model = app.store.getBy('tags', 'slug', tag.getAttribute('slug'));\n\n if (model) {\n tag.setAttribute('id', model.id());\n tag.setAttribute('tagname', model.name());\n\n return true;\n }\n }\n\n tag.invalidate();\n}\n\nexport function postFilterTagMentions(tag) {\n if ('flarum-tags' in flarum.extensions) {\n const model = app.store.getBy('tags', 'slug', tag.getAttribute('slug'));\n\n tag.setAttribute('icon', model.icon());\n tag.setAttribute('color', model.color());\n tag.setAttribute('deleted', false);\n }\n}\n","import GroupMentionedNotification from './components/GroupMentionedNotification';\nimport MentionsUserPage from './components/MentionsUserPage';\nimport PostMentionedNotification from './components/PostMentionedNotification';\nimport UserMentionedNotification from './components/UserMentionedNotification';\nimport AutocompleteDropdown from './fragments/AutocompleteDropdown';\nimport PostQuoteButton from './fragments/PostQuoteButton';\nimport getCleanDisplayName from './utils/getCleanDisplayName';\nimport getMentionText from './utils/getMentionText';\nimport * as reply from './utils/reply';\nimport selectedText from './utils/selectedText';\nimport * as textFormatter from './utils/textFormatter';\nimport MentionableModel from './mentionables/MentionableModel';\nimport MentionFormat from './mentionables/formats/MentionFormat';\nimport Mentionables from './extenders/Mentionables';\n\nexport default {\n 'mentions/components/MentionsUserPage': MentionsUserPage,\n 'mentions/components/PostMentionedNotification': PostMentionedNotification,\n 'mentions/components/UserMentionedNotification': UserMentionedNotification,\n 'mentions/components/GroupMentionedNotification': GroupMentionedNotification,\n 'mentions/fragments/AutocompleteDropdown': AutocompleteDropdown,\n 'mentions/fragments/PostQuoteButton': PostQuoteButton,\n 'mentions/utils/getCleanDisplayName': getCleanDisplayName,\n 'mentions/utils/getMentionText': getMentionText,\n 'mentions/utils/reply': reply,\n 'mentions/utils/selectedText': selectedText,\n 'mentions/utils/textFormatter': textFormatter,\n 'mentions/mentionables/MentionableModel': MentionableModel,\n 'mentions/mentionables/formats/MentionFormat': MentionFormat,\n 'mentions/extenders/Mentionables': Mentionables,\n};\n","import app from 'flarum/forum/app';\n\n/**\n * Fetches the mention text for a specified user (and optionally a post ID for replies or group).\n *\n * Automatically determines which mention syntax to be used based on the option in the\n * admin dashboard. Also performs display name clean-up automatically.\n *\n * @deprecated Use `app.mentionables.get('user').replacement(user)` instead. Will be removed in 2.0.\n */\nexport default function getMentionText(user, postId, group) {\n if (user !== undefined && postId === undefined) {\n return app.mentionables.get('user').replacement(user);\n } else if (user !== undefined && postId !== undefined) {\n return app.mentionables.get('post').replacement(app.store.getById('posts', postId));\n } else if (group !== undefined) {\n return app.mentionables.get('group').replacement(group);\n }\n\n throw 'No parameters were passed';\n}\n","import type ForumApplication from 'flarum/forum/ForumApplication';\nimport type IExtender from 'flarum/common/extenders/IExtender';\nimport type MentionableModel from '../mentionables/MentionableModel';\nimport type MentionFormat from '../mentionables/formats/MentionFormat';\n\nexport default class Mentionables implements IExtender {\n protected formats: (new () => MentionFormat)[] = [];\n protected mentionables: Record MentionableModel)[]> = {};\n\n /**\n * Register a new mention format.\n * Must extend MentionFormat and have a unique unused trigger symbol.\n */\n format(format: new () => MentionFormat): this {\n this.formats.push(format);\n\n return this;\n }\n\n /**\n * Register a new mentionable model to a mention format.\n * Only works if the format has already been registered,\n * and the format allows using multiple mentionables.\n *\n * @param symbol The trigger symbol of the format to extend (ex: @).\n * @param mentionable The mentionable instance to register.\n * Must extend MentionableModel.\n */\n mentionable(symbol: string, mentionable: new () => MentionableModel): this {\n if (!this.mentionables[symbol]) {\n this.mentionables[symbol] = [];\n }\n\n this.mentionables[symbol].push(mentionable);\n\n return this;\n }\n\n extend(app: ForumApplication): void {\n for (const format of this.formats) {\n app.mentionFormats.extend(format);\n }\n\n for (const symbol in this.mentionables) {\n const format = app.mentionFormats.get(symbol);\n\n if (!format) continue;\n\n for (const mentionable of this.mentionables[symbol]) {\n format.extend(mentionable);\n }\n }\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core;","import { extend } from 'flarum/common/extend';\nimport app from 'flarum/forum/app';\nimport NotificationGrid from 'flarum/forum/components/NotificationGrid';\nimport { getPlainContent } from 'flarum/common/utils/string';\nimport textContrastClass from 'flarum/common/helpers/textContrastClass';\nimport Post from 'flarum/forum/components/Post';\n\nimport addPostMentionPreviews from './addPostMentionPreviews';\nimport addMentionedByList from './addMentionedByList';\nimport addPostReplyAction from './addPostReplyAction';\nimport addPostQuoteButton from './addPostQuoteButton';\nimport addComposerAutocomplete from './addComposerAutocomplete';\nimport PostMentionedNotification from './components/PostMentionedNotification';\nimport UserMentionedNotification from './components/UserMentionedNotification';\nimport GroupMentionedNotification from './components/GroupMentionedNotification';\nimport UserPage from 'flarum/forum/components/UserPage';\nimport LinkButton from 'flarum/common/components/LinkButton';\nimport User from 'flarum/common/models/User';\nimport Model from 'flarum/common/Model';\n\nexport { default as extend } from './extend';\n\napp.initializers.add('flarum-mentions', function () {\n // For every mention of a post inside a post's content, set up a hover handler\n // that shows a preview of the mentioned post.\n addPostMentionPreviews();\n\n // In the footer of each post, show information about who has replied (i.e.\n // who the post has been mentioned by).\n addMentionedByList();\n\n // Add a 'reply' control to the footer of each post. When clicked, it will\n // open up the composer and add a post mention to its contents.\n addPostReplyAction();\n\n // Show a Quote button when Post text is selected\n addPostQuoteButton();\n\n // After typing '@' in the composer, show a dropdown suggesting a bunch of\n // posts or users that the user could mention.\n addComposerAutocomplete();\n\n app.notificationComponents.postMentioned = PostMentionedNotification;\n app.notificationComponents.userMentioned = UserMentionedNotification;\n app.notificationComponents.groupMentioned = GroupMentionedNotification;\n\n // Add notification preferences.\n extend(NotificationGrid.prototype, 'notificationTypes', function (items) {\n items.add('postMentioned', {\n name: 'postMentioned',\n icon: 'fas fa-reply',\n label: app.translator.trans('flarum-mentions.forum.settings.notify_post_mentioned_label'),\n });\n\n items.add('userMentioned', {\n name: 'userMentioned',\n icon: 'fas fa-at',\n label: app.translator.trans('flarum-mentions.forum.settings.notify_user_mentioned_label'),\n });\n\n items.add('groupMentioned', {\n name: 'groupMentioned',\n icon: 'fas fa-at',\n label: app.translator.trans('flarum-mentions.forum.settings.notify_group_mentioned_label'),\n });\n });\n\n // Add mentions tab in user profile\n extend(UserPage.prototype, 'navItems', function (items) {\n const user = this.user;\n items.add(\n 'mentions',\n \n {app.translator.trans('flarum-mentions.forum.user.mentions_link')}\n ,\n 80\n );\n });\n\n // Remove post mentions when rendering post previews.\n getPlainContent.removeSelectors.push('a.PostMention');\n\n // Apply color contrast fix on group mentions.\n extend(Post.prototype, 'oncreate', function () {\n this.$('.GroupMention--colored, .TagMention--colored').each(function () {\n this.classList.add(textContrastClass(getComputedStyle(this).getPropertyValue('--color')));\n });\n });\n});\n\nexport * from './utils/textFormatter';\n\n// Expose compat API\nimport mentionsCompat from './compat';\nimport { compat } from '@flarum/core/forum';\n\nObject.assign(compat, mentionsCompat);\n","import { extend } from 'flarum/common/extend';\nimport CommentPost from 'flarum/forum/components/CommentPost';\nimport PostPreview from 'flarum/forum/components/PostPreview';\nimport LoadingIndicator from 'flarum/common/components/LoadingIndicator';\n\nexport default function addPostMentionPreviews() {\n function addPreviews() {\n const contentHtml = this.attrs.post.contentHtml();\n\n if (contentHtml === this.oldPostContentHtml || this.isEditing()) return;\n\n this.oldPostContentHtml = contentHtml;\n\n const parentPost = this.attrs.post;\n const $parentPost = this.$();\n\n this.$().on(\n 'click',\n '.UserMention:not(.UserMention--deleted), .PostMention:not(.PostMention--deleted), .TagMention:not(.TagMention--deleted)',\n function (e) {\n m.route.set(this.getAttribute('href'));\n e.preventDefault();\n }\n );\n\n this.$('.PostMention:not(.PostMention--deleted)').each(function () {\n const $this = $(this);\n const id = $this.data('id');\n let timeout;\n\n // Wrap the mention link in a wrapper element so that we can insert a\n // preview popup as its sibling and relatively position it.\n const $preview = $('
');\n $parentPost.append($preview);\n\n const getPostElement = () => {\n return $(`.PostStream-item[data-id=\"${id}\"]`);\n };\n\n const showPreview = () => {\n // When the user hovers their mouse over the mention, look for the\n // post that it's referring to in the stream, and determine if it's\n // in the viewport. If it is, we will \"pulsate\" it.\n const $post = getPostElement();\n let visible = false;\n if ($post.length) {\n const top = $post.offset().top;\n const scrollTop = window.pageYOffset;\n if (top > scrollTop && top + $post.height() < scrollTop + $(window).height()) {\n $post.addClass('pulsate');\n visible = true;\n }\n }\n\n // Otherwise, we will show a popup preview of the post. If the post\n // hasn't yet been loaded, we will need to do that.\n if (!visible) {\n // Position the preview so that it appears above the mention.\n // (The offsetParent should be .Post-body.)\n const positionPreview = () => {\n const previewHeight = $preview.outerHeight(true);\n let offset = 0;\n\n // If the preview goes off the top of the viewport, reposition it to\n // be below the mention.\n if ($this.offset().top - previewHeight < $(window).scrollTop() + $('#header').outerHeight()) {\n offset += $this.outerHeight(true);\n } else {\n offset -= previewHeight;\n }\n\n $preview\n .show()\n .css('top', $this.offset().top - $parentPost.offset().top + offset)\n .css('left', $this.offsetParent().offset().left - $parentPost.offset().left)\n .css('max-width', $this.offsetParent().width());\n };\n\n const showPost = (post) => {\n const discussion = post.discussion();\n\n m.render($preview[0], [\n discussion !== parentPost.discussion() && (\n
\n {discussion.title()}\n
\n ),\n
\n \n
,\n ]);\n positionPreview();\n };\n\n const post = app.store.getById('posts', id);\n if (post && post.discussion()) {\n showPost(post);\n } else {\n m.render($preview[0], );\n app.store.find('posts', id).then(showPost);\n positionPreview();\n }\n\n setTimeout(() => $preview.off('transitionend').addClass('in'));\n }\n };\n\n const hidePreview = () => {\n getPostElement().removeClass('pulsate');\n if ($preview.hasClass('in')) {\n $preview.removeClass('in').one('transitionend', () => $preview.hide());\n }\n };\n\n // On a touch (mobile) device we cannot hover the link to reveal the preview.\n // Instead we cancel the navigation so that a click reveals the preview.\n // Users can then click on the preview to go to the post if desired.\n $this.on('touchend', (e) => {\n if (e.cancelable) {\n e.preventDefault();\n }\n });\n\n $this\n .add($preview)\n .hover(\n () => {\n clearTimeout(timeout);\n timeout = setTimeout(showPreview, 250);\n },\n () => {\n clearTimeout(timeout);\n getPostElement().removeClass('pulsate');\n timeout = setTimeout(hidePreview, 250);\n }\n )\n .on('touchend', (e) => {\n showPreview();\n e.stopPropagation();\n });\n\n $(document).on('touchend', hidePreview);\n });\n }\n\n extend(CommentPost.prototype, 'oncreate', addPreviews);\n extend(CommentPost.prototype, 'onupdate', addPreviews);\n}\n","import app from 'flarum/forum/app';\nimport { extend } from 'flarum/common/extend';\nimport CommentPost from 'flarum/forum/components/CommentPost';\nimport Link from 'flarum/common/components/Link';\nimport PostPreview from 'flarum/forum/components/PostPreview';\nimport punctuateSeries from 'flarum/common/helpers/punctuateSeries';\nimport username from 'flarum/common/helpers/username';\nimport icon from 'flarum/common/helpers/icon';\nimport Button from 'flarum/common/components/Button';\nimport MentionedByModal from './components/MentionedByModal';\n\nexport default function addMentionedByList() {\n function hidePreview() {\n this.$('.Post-mentionedBy-preview')\n .removeClass('in')\n .one('transitionend', function () {\n $(this).hide();\n });\n }\n\n extend(CommentPost.prototype, 'oncreate', function () {\n let timeout;\n const post = this.attrs.post;\n const replies = post.mentionedBy();\n\n if (replies && replies.length) {\n const $preview = $('
');\n this.$().append($preview);\n\n const $parentPost = this.$();\n const $this = this.$('.Post-mentionedBy');\n\n const showPreview = () => {\n if (!$preview.hasClass('in') && $preview.is(':visible')) return;\n\n // When the user hovers their mouse over the list of people who have\n // replied to the post, render a list of reply previews into a\n // popup.\n m.render(\n $preview[0],\n <>\n {replies.map((reply) => (\n
\n )}\n >\n );\n\n $preview\n .show()\n .css('top', $this.offset().top - $parentPost.offset().top + $this.outerHeight(true))\n .css('left', $this.offsetParent().offset().left - $parentPost.offset().left)\n .css('max-width', $parentPost.width());\n\n setTimeout(() => $preview.off('transitionend').addClass('in'));\n };\n\n $this.add($preview).hover(\n () => {\n clearTimeout(timeout);\n timeout = setTimeout(showPreview, 250);\n },\n () => {\n clearTimeout(timeout);\n timeout = setTimeout(hidePreview, 250);\n }\n );\n\n // Whenever the user hovers their mouse over a particular name in the\n // list of repliers, highlight the corresponding post in the preview\n // popup.\n this.$()\n .find('.Post-mentionedBy-summary a')\n .hover(\n function () {\n $preview.find('[data-number=\"' + $(this).data('number') + '\"]').addClass('active');\n },\n function () {\n $preview.find('[data-number]').removeClass('active');\n }\n );\n }\n });\n\n extend(CommentPost.prototype, 'footerItems', function (items) {\n const post = this.attrs.post;\n const replies = post.mentionedBy();\n\n if (replies && replies.length) {\n const users = [];\n const repliers = replies\n .sort((reply) => (reply.user() === app.session.user ? -1 : 0))\n .filter((reply) => {\n const user = reply.user();\n if (users.indexOf(user) === -1) {\n users.push(user);\n return true;\n }\n });\n\n const limit = 4;\n const overLimit = repliers.length > limit;\n\n // Create a list of unique users who have replied. So even if a user has\n // replied twice, they will only be in this array once.\n const names = repliers.slice(0, overLimit ? limit - 1 : limit).map((reply) => {\n const user = reply.user();\n\n return (\n \n {app.session.user === user ? app.translator.trans('flarum-mentions.forum.post.you_text') : username(user)}\n \n );\n });\n\n // If there are more users that we've run out of room to display, add a \"x\n // others\" name to the end of the list. Clicking on it will display a modal\n // with a full list of names.\n if (overLimit) {\n const count = repliers.length - names.length;\n\n names.push(app.translator.trans('flarum-mentions.forum.post.others_text', { count }));\n }\n\n items.add(\n 'replies',\n
\n );\n }\n });\n}\n","import app from 'flarum/forum/app';\nimport { extend } from 'flarum/common/extend';\nimport Button from 'flarum/common/components/Button';\nimport CommentPost from 'flarum/forum/components/CommentPost';\n\nimport reply from './utils/reply';\n\nexport default function () {\n extend(CommentPost.prototype, 'actionItems', function (items) {\n const post = this.attrs.post;\n\n if (post.isHidden() || (app.session.user && !post.discussion().canReply())) return;\n\n items.add(\n 'reply',\n \n );\n });\n}\n","import app from 'flarum/forum/app';\nimport { extend } from 'flarum/common/extend';\nimport CommentPost from 'flarum/forum/components/CommentPost';\n\nimport PostQuoteButton from './fragments/PostQuoteButton';\nimport selectedText from './utils/selectedText';\n\nexport default function addPostQuoteButton() {\n extend(CommentPost.prototype, 'oncreate', function () {\n const post = this.attrs.post;\n\n if (post.isHidden() || (app.session.user && !post.discussion().canReply())) return;\n\n const $postBody = this.$('.Post-body');\n\n // Wrap the quote button in a wrapper element so that we can render\n // button into it.\n const $container = $('');\n\n const button = new PostQuoteButton(post);\n\n const handler = function (e) {\n setTimeout(() => {\n const content = selectedText($postBody);\n if (content) {\n button.content = content;\n m.render($container[0], button.render());\n\n const rects = window.getSelection().getRangeAt(0).getClientRects();\n const firstRect = rects[0];\n\n if (e.clientY < firstRect.bottom && e.clientX - firstRect.right < firstRect.left - e.clientX) {\n button.showStart(firstRect.left, firstRect.top);\n } else {\n const lastRect = rects[rects.length - 1];\n button.showEnd(lastRect.right, lastRect.bottom);\n }\n }\n }, 1);\n };\n\n this.$().after($container).on('mouseup', handler);\n\n if ('ontouchstart' in window) {\n document.addEventListener('selectionchange', handler, false);\n }\n });\n}\n","import app from 'flarum/forum/app';\nimport { extend } from 'flarum/common/extend';\nimport TextEditor from 'flarum/common/components/TextEditor';\nimport TextEditorButton from 'flarum/common/components/TextEditorButton';\nimport KeyboardNavigatable from 'flarum/common/utils/KeyboardNavigatable';\n\nimport AutocompleteDropdown from './fragments/AutocompleteDropdown';\nimport MentionFormats from './mentionables/formats/MentionFormats';\nimport MentionableModels from './mentionables/MentionableModels';\n\nexport default function addComposerAutocomplete() {\n app.mentionFormats = new MentionFormats();\n\n const $container = $('');\n const dropdown = new AutocompleteDropdown();\n\n extend(TextEditor.prototype, 'oncreate', function () {\n const $editor = this.$('.TextEditor-editor').wrap('');\n\n this.navigator = new KeyboardNavigatable();\n this.navigator\n .when(() => dropdown.active)\n .onUp(() => dropdown.navigate(-1))\n .onDown(() => dropdown.navigate(1))\n .onSelect(dropdown.complete.bind(dropdown))\n .onCancel(dropdown.hide.bind(dropdown))\n .bindTo($editor);\n\n $editor.after($container);\n });\n\n extend(TextEditor.prototype, 'buildEditorParams', function (params) {\n let relMentionStart;\n let absMentionStart;\n let matchTyped;\n\n let mentionables = new MentionableModels({\n onmouseenter: function () {\n dropdown.setIndex($(this).parent().index());\n },\n onclick: (replacement) => {\n this.attrs.composer.editor.replaceBeforeCursor(absMentionStart - 1, replacement + ' ');\n\n dropdown.hide();\n },\n });\n\n const suggestionsInputListener = () => {\n const selection = this.attrs.composer.editor.getSelectionRange();\n\n const cursor = selection[0];\n\n if (selection[1] - cursor > 0) return;\n\n // Search backwards from the cursor for a mention triggering symbol. If we find one,\n // we will want to show the correct autocomplete dropdown!\n // Check classes implementing the IMentionableModel interface to see triggering symbols.\n const lastChunk = this.attrs.composer.editor.getLastNChars(30);\n absMentionStart = 0;\n let activeFormat = null;\n for (let i = lastChunk.length - 1; i >= 0; i--) {\n const character = lastChunk.substr(i, 1);\n activeFormat = app.mentionFormats.get(character);\n\n if (activeFormat && (i === 0 || /\\s/.test(lastChunk.substr(i - 1, 1)))) {\n relMentionStart = i + 1;\n absMentionStart = cursor - lastChunk.length + i + 1;\n mentionables.init(activeFormat.makeMentionables());\n break;\n }\n }\n\n dropdown.hide();\n dropdown.active = false;\n\n if (absMentionStart) {\n const typed = lastChunk.substring(relMentionStart).toLowerCase();\n matchTyped = activeFormat.queryFromTyped(typed);\n\n if (!matchTyped) return;\n\n mentionables.typed = matchTyped;\n\n const buildSuggestions = () => {\n // If the user has started to type a mention,\n // then suggest models matching.\n const suggestions = mentionables.buildSuggestions();\n\n if (suggestions.length) {\n dropdown.items = suggestions;\n m.render($container[0], dropdown.render());\n\n dropdown.show();\n const coordinates = this.attrs.composer.editor.getCaretCoordinates(absMentionStart);\n const width = dropdown.$().outerWidth();\n const height = dropdown.$().outerHeight();\n const parent = dropdown.$().offsetParent();\n let left = coordinates.left;\n let top = coordinates.top + 15;\n\n // Keep the dropdown inside the editor.\n if (top + height > parent.height()) {\n top = coordinates.top - height - 15;\n }\n if (left + width > parent.width()) {\n left = parent.width() - width;\n }\n\n // Prevent the dropdown from going off screen on mobile\n top = Math.max(-(parent.offset().top - $(document).scrollTop()), top);\n left = Math.max(-parent.offset().left, left);\n\n dropdown.show(left, top);\n } else {\n dropdown.active = false;\n dropdown.hide();\n }\n };\n\n dropdown.active = true;\n\n buildSuggestions();\n\n dropdown.setIndex(0);\n dropdown.$().scrollTop(0);\n\n mentionables.search()?.then(buildSuggestions);\n }\n };\n\n params.inputListeners.push(suggestionsInputListener);\n });\n\n extend(TextEditor.prototype, 'toolbarItems', function (items) {\n items.add(\n 'mention',\n this.attrs.composer.editor.insertAtCursor(' @')} icon=\"fas fa-at\">\n {app.translator.trans('flarum-mentions.forum.composer.mention_tooltip')}\n \n );\n });\n}\n"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Symbol","toStringTag","value","flarum","core","compat","MentionedByModalState","PaginatedListState","constructor","params","page","limit","super","type","MentionedByModal","Modal","oninit","vnode","this","state","filter","mentionedPost","attrs","post","id","sort","refresh","className","title","app","content","m","'['","isInitialLoading","LoadingIndicator","getPages","map","items","reply","number","PostPreview","onclick","hasNext","Button","loadNext","loading","isLoadingNext","insertMention","composer","quote","Promise","resolve","mention","replacement","fields","body","originalContent","cursorPosition","editor","getSelectionRange","preceding","slice","precedingNewlines","length","match","insertAtCursor","Array","join","trim","replace","EditPostComposer","discussion","DiscussionControls","then","PostQuoteButton","Fragment","view","icon","show","left","top","$this","$","parentOffset","offsetParent","offset","css","hideHandler","hide","bind","document","on","showStart","window","scrollTop","outerHeight","showEnd","right","bottom","outerWidth","off","selectedText","selection","getSelection","isCollapsed","range","getRangeAt","parent","commonAncestorContainer","contains","clone","append","cloneContents","find","replaceWith","alt","concat","src","innerText","href","text","_typeof","iterator","_defineProperty","arg","input","hint","prim","toPrimitive","undefined","res","TypeError","String","toPropertyKey","configurable","writable","AutocompleteDropdown","arguments","item","active","navigate","delta","keyWasJustPressed","setIndex","index","clearTimeout","keyWasJustPressedTimeout","setTimeout","complete","eq","click","scrollToItem","$dropdown","$items","rangedIndex","$item","removeClass","addClass","dropdownScroll","dropdownTop","dropdownBottom","itemTop","itemBottom","parseInt","stop","animate","MentionFormat","makeMentionables","_this$instances","instances","mentionables","Mentionable","getMentionable","_this$makeMentionable","mentionable","extend","extendable","Error","push","MentionableModel","format","getDeletedUserText","extractText","getCleanDisplayName","user","useDisplayName","displayName","username","UserMention","initialResults","from","cleanText","suggestion","model","typed","usernameHelper","name","highlight","avatar","matches","some","toLowerCase","substr","maxStoreMatchedResults","async","q","enabled","PostMention","ReplyComposer","composerAttrs","composerPost","posts","contentType","b","createdAt","getTime","_model$contentPlain","truncate","contentPlain","userMentionable","search","GroupMention","g","Group","group","namePlural","groupName","Badge","color","nameSingular","_app$session$user$can","_app$session","_app$session$user","canMentionGroups","AtMentionFormat","trigger","queryFromTyped","matchTyped","char","simple","safe","TagMention","tag","slug","tagName","extensions","HashMentionFormat","MentionFormats","symbol","_this$formats$find","formats","f","MentionsDropdownItem","Component","classList","assign","children","MentionableModels","dropdownItemAttrs","throttle","typedLower","searched","includes","results","has","set","init","Map","result","_this$typed","makeSuggestion","buildSuggestions","suggestions","values","max","splice","dropdownItem","PostMentionedNotification","Notification","notification","subject","replyNumber","fromUser","count","excerpt","UserMentionedNotification","GroupMentionedNotification","MentionsUserPage","PostsUserPage","loadResults","mentioned","loadLimit","Extend","add","Post","hasMany","attribute","User","filterUserMentions","hasAttribute","getAttribute","setAttribute","invalidate","postFilterUserMentions","filterPostMentions","postFilterPostMentions","filterGroupMentions","postFilterGroupMentions","_app$session2","_app$session2$user","filterTagMentions","postFilterTagMentions","postId","textFormatter","mentionFormats","addPreviews","contentHtml","oldPostContentHtml","isEditing","parentPost","$parentPost","e","route","preventDefault","each","data","timeout","$preview","getPostElement","showPreview","$post","visible","pageYOffset","height","positionPreview","previewHeight","width","showPost","render","store","getById","hidePreview","hasClass","one","cancelable","hover","stopPropagation","CommentPost","addPostMentionPreviews","replies","mentionedBy","is","mentionedByCount","users","repliers","indexOf","overLimit","names","Link","punctuateSeries","addMentionedByList","isHidden","canReply","$postBody","$container","button","handler","rects","getClientRects","firstRect","clientY","clientX","lastRect","after","addEventListener","dropdown","TextEditor","$editor","wrap","navigator","KeyboardNavigatable","when","onUp","onDown","onSelect","onCancel","bindTo","relMentionStart","absMentionStart","onmouseenter","replaceBeforeCursor","inputListeners","suggestionsInputListener","cursor","lastChunk","getLastNChars","activeFormat","i","character","test","_mentionables$search","substring","coordinates","getCaretCoordinates","Math","TextEditorButton","addComposerAutocomplete","NotificationGrid","label","UserPage","LinkButton","getPlainContent","textContrastClass","getComputedStyle","getPropertyValue","mentionsCompat"],"sourceRoot":""}
\ No newline at end of file
+{"version":3,"file":"forum.js","mappings":"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","sources":["webpack://@flarum/mentions/webpack/bootstrap","webpack://@flarum/mentions/webpack/runtime/compat get default export","webpack://@flarum/mentions/webpack/runtime/define property getters","webpack://@flarum/mentions/webpack/runtime/hasOwnProperty shorthand","webpack://@flarum/mentions/webpack/runtime/make namespace object","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/extend')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'forum/app')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/utils/string')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/helpers/textContrastClass')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'forum/components/Post')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'forum/components/CommentPost')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'forum/components/PostPreview')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/components/LoadingIndicator')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/components/Link')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/helpers/punctuateSeries')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/helpers/username')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/components/Icon')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/components/Button')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/components/Modal')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/states/PaginatedListState')\"","webpack://@flarum/mentions/./src/forum/state/MentionedByModalState.ts","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/components/Form')\"","webpack://@flarum/mentions/./src/forum/components/MentionedByModal.tsx","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'forum/utils/DiscussionControls')\"","webpack://@flarum/mentions/./src/forum/utils/reply.js","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/Fragment')\"","webpack://@flarum/mentions/./src/forum/fragments/PostQuoteButton.js","webpack://@flarum/mentions/./src/forum/utils/selectedText.js","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/components/TextEditorButton')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/utils/KeyboardNavigatable')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/utils/AutocompleteReader')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/utils/throttleDebounce')\"","webpack://@flarum/mentions/../../../node_modules/@babel/runtime/helpers/esm/typeof.js","webpack://@flarum/mentions/../../../node_modules/@babel/runtime/helpers/esm/defineProperty.js","webpack://@flarum/mentions/../../../node_modules/@babel/runtime/helpers/esm/toPropertyKey.js","webpack://@flarum/mentions/../../../node_modules/@babel/runtime/helpers/esm/toPrimitive.js","webpack://@flarum/mentions/./src/forum/fragments/AutocompleteDropdown.js","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/Component')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/utils/classList')\"","webpack://@flarum/mentions/./src/forum/components/MentionsDropdownItem.tsx","webpack://@flarum/mentions/./src/forum/mentionables/MentionableModels.tsx","webpack://@flarum/mentions/./src/forum/mentionables/formats/MentionFormat.ts","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/components/Avatar')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/helpers/highlight')\"","webpack://@flarum/mentions/./src/forum/mentionables/MentionableModel.ts","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/utils/extractText')\"","webpack://@flarum/mentions/./src/forum/utils/getCleanDisplayName.js","webpack://@flarum/mentions/./src/forum/mentionables/UserMention.tsx","webpack://@flarum/mentions/./src/forum/mentionables/PostMention.tsx","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/models/Group')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/components/Badge')\"","webpack://@flarum/mentions/./src/forum/mentionables/GroupMention.tsx","webpack://@flarum/mentions/./src/forum/mentionables/formats/AtMentionFormat.ts","webpack://@flarum/mentions/./src/forum/mentionables/TagMention.tsx","webpack://@flarum/mentions/./src/forum/mentionables/formats/HashMentionFormat.ts","webpack://@flarum/mentions/./src/forum/mentionables/formats/MentionFormats.ts","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'forum/components/UserPage')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/components/LinkButton')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/extenders')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/models/Post')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/models/User')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'forum/components/PostsUserPage')\"","webpack://@flarum/mentions/./src/forum/components/MentionsUserPage.ts","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'forum/components/Notification')\"","webpack://@flarum/mentions/./src/forum/components/PostMentionedNotification.js","webpack://@flarum/mentions/./src/forum/components/UserMentionedNotification.js","webpack://@flarum/mentions/./src/forum/components/GroupMentionedNotification.js","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/query/IGambit')\"","webpack://@flarum/mentions/external root \"flarum.reg.get('core', 'common/app')\"","webpack://@flarum/mentions/./src/common/query/posts/MentionedGambit.ts","webpack://@flarum/mentions/./src/common/extend.ts","webpack://@flarum/mentions/./src/forum/extend.ts","webpack://@flarum/mentions/./src/forum/utils/textFormatter.js","webpack://@flarum/mentions/./src/forum/extenders/Mentionables.ts","webpack://@flarum/mentions/./src/forum/index.js","webpack://@flarum/mentions/./src/forum/addPostMentionPreviews.js","webpack://@flarum/mentions/./src/forum/addMentionedByList.js","webpack://@flarum/mentions/./src/forum/addPostReplyAction.js","webpack://@flarum/mentions/./src/forum/addPostQuoteButton.js","webpack://@flarum/mentions/./src/forum/addComposerAutocomplete.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/extend');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/app');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/utils/string');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/helpers/textContrastClass');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/components/Post');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/components/CommentPost');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/components/PostPreview');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/LoadingIndicator');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Link');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/helpers/punctuateSeries');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/helpers/username');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Icon');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Button');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Modal');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/states/PaginatedListState');","import PaginatedListState from 'flarum/common/states/PaginatedListState';\nexport default class MentionedByModalState extends PaginatedListState {\n constructor(params, page) {\n if (page === void 0) {\n page = 1;\n }\n const limit = 10;\n params.page = {\n ...(params.page || {}),\n limit\n };\n super(params, page, limit);\n }\n get type() {\n return 'posts';\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/state/MentionedByModalState', MentionedByModalState);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Form');","import app from 'flarum/forum/app';\nimport PostPreview from 'flarum/forum/components/PostPreview';\nimport Modal from 'flarum/common/components/Modal';\nimport LoadingIndicator from 'flarum/common/components/LoadingIndicator';\nimport Button from 'flarum/common/components/Button';\nimport MentionedByModalState from '../state/MentionedByModalState';\nimport Form from 'flarum/common/components/Form';\nexport default class MentionedByModal extends Modal {\n oninit(vnode) {\n super.oninit(vnode);\n this.state = new MentionedByModalState({\n filter: {\n mentionedPost: this.attrs.post.id()\n },\n sort: 'number'\n });\n this.state.refresh();\n }\n className() {\n return 'MentionedByModal';\n }\n title() {\n return app.translator.trans('flarum-mentions.forum.mentioned_by.title');\n }\n content() {\n return m('[', null, m(\"div\", {\n className: \"Modal-body\"\n }, this.state.isInitialLoading() ? m(LoadingIndicator, null) : m('[', null, m(\"ul\", {\n className: \"MentionedByModal-list Dropdown-menu Dropdown-menu--inline Post-mentionedBy-preview\"\n }, this.state.getPages().map(page => page.items.map(reply => m(\"li\", {\n \"data-number\": reply.number()\n }, m(PostPreview, {\n post: reply,\n onclick: () => app.modal.close()\n }))))))), this.state.hasNext() && m(\"div\", {\n className: \"Modal-footer\"\n }, m(Form, {\n className: \"Form--centered\"\n }, m(\"div\", {\n className: \"Form-group\"\n }, m(Button, {\n className: \"Button Button--block\",\n onclick: () => this.state.loadNext(),\n loading: this.state.isLoadingNext()\n }, app.translator.trans('flarum-mentions.forum.mentioned_by.load_more_button'))))));\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/components/MentionedByModal', MentionedByModal);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/utils/DiscussionControls');","import app from 'flarum/forum/app';\nimport DiscussionControls from 'flarum/forum/utils/DiscussionControls';\nexport function insertMention(post, composer, quote) {\n return new Promise(resolve => {\n const mention = app.mentionFormats.mentionable('post').replacement(post) + ' ';\n\n // If the composer is empty, then assume we're starting a new reply.\n // In which case we don't want the user to have to confirm if they\n // close the composer straight away.\n if (!composer.fields.content()) {\n composer.body.attrs.originalContent = mention;\n }\n const cursorPosition = composer.editor.getSelectionRange()[0];\n const preceding = composer.fields.content().slice(0, cursorPosition);\n const precedingNewlines = preceding.length == 0 ? 0 : 3 - preceding.match(/(\\n{0,2})$/)[0].length;\n composer.editor.insertAtCursor(Array(precedingNewlines).join('\\n') + (\n // Insert up to two newlines, depending on preceding whitespace\n quote ? '> ' + mention + quote.trim().replace(/\\n/g, '\\n> ') + '\\n\\n' : mention), false);\n return resolve(composer);\n });\n}\nexport default function reply(post, quote) {\n const EditPostComposer = flarum.reg.checkModule('core', 'forum/components/EditPostComposer');\n if (EditPostComposer && app.composer.bodyMatches(EditPostComposer) && app.composer.body.attrs.post.discussion() === post.discussion()) {\n // If we're already editing a post in the discussion of post we're quoting,\n // insert the mention directly.\n return insertMention(post, app.composer, quote);\n } else {\n // The default \"Reply\" action behavior will only open a new composer if\n // necessary, but it will always be a ReplyComposer, hence the exceptional\n // case above.\n return DiscussionControls.replyAction.call(post.discussion()).then(composer => insertMention(post, composer, quote));\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/utils/reply', reply);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/Fragment');","import app from 'flarum/forum/app';\nimport Fragment from 'flarum/common/Fragment';\nimport Icon from 'flarum/common/components/Icon';\nimport reply from '../utils/reply';\nexport default class PostQuoteButton extends Fragment {\n constructor(post) {\n super();\n this.post = post;\n }\n view() {\n return m(\"button\", {\n className: \"Button PostQuoteButton\",\n onclick: () => {\n reply(this.post, this.content);\n }\n }, m(Icon, {\n name: \"fas fa-quote-left\",\n className: \"Button-icon\"\n }), app.translator.trans('flarum-mentions.forum.post.quote_button'));\n }\n show(left, top) {\n const $this = this.$().show();\n const parentOffset = $this.offsetParent().offset();\n $this.css('left', left - parentOffset.left).css('top', top - parentOffset.top);\n this.hideHandler = this.hide.bind(this);\n $(document).on('mouseup', this.hideHandler);\n }\n showStart(left, top) {\n const $this = this.$();\n this.show(left, $(window).scrollTop() + top - $this.outerHeight() - 5);\n }\n showEnd(right, bottom) {\n const $this = this.$();\n this.show(right - $this.outerWidth(), $(window).scrollTop() + bottom + 5);\n }\n hide() {\n this.$().hide();\n $(document).off('mouseup', this.hideHandler);\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/fragments/PostQuoteButton', PostQuoteButton);","/**\n * Finds the selected text in the provided composer body.\n */\nexport default function selectedText(body) {\n const selection = window.getSelection();\n if (!selection.isCollapsed) {\n const range = selection.getRangeAt(0);\n const parent = range.commonAncestorContainer;\n if (body[0] === parent || $.contains(body[0], parent)) {\n const clone = $('
').append(range.cloneContents());\n\n // Replace emoji images with their shortcode (found in alt attribute)\n clone.find('img.emoji').replaceWith(function () {\n return this.alt;\n });\n\n // Replace all other images with a Markdown image\n clone.find('img').replaceWith(function () {\n return `![](${this.src})`;\n });\n\n // Replace all links with a Markdown link\n clone.find('a').replaceWith(function () {\n return `[${this.innerText}](${this.href})`;\n });\n return clone.text();\n }\n }\n return '';\n}\nflarum.reg.add('flarum-mentions', 'forum/utils/selectedText', selectedText);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/TextEditorButton');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/utils/KeyboardNavigatable');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/utils/AutocompleteReader');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/utils/throttleDebounce');","function _typeof(o) {\n \"@babel/helpers - typeof\";\n\n return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) {\n return typeof o;\n } : function (o) {\n return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n }, _typeof(o);\n}\nexport { _typeof as default };","import toPropertyKey from \"./toPropertyKey.js\";\nfunction _defineProperty(e, r, t) {\n return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n value: t,\n enumerable: !0,\n configurable: !0,\n writable: !0\n }) : e[r] = t, e;\n}\nexport { _defineProperty as default };","import _typeof from \"./typeof.js\";\nimport toPrimitive from \"./toPrimitive.js\";\nfunction toPropertyKey(t) {\n var i = toPrimitive(t, \"string\");\n return \"symbol\" == _typeof(i) ? i : i + \"\";\n}\nexport { toPropertyKey as default };","import _typeof from \"./typeof.js\";\nfunction toPrimitive(t, r) {\n if (\"object\" != _typeof(t) || !t) return t;\n var e = t[Symbol.toPrimitive];\n if (void 0 !== e) {\n var i = e.call(t, r || \"default\");\n if (\"object\" != _typeof(i)) return i;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (\"string\" === r ? String : Number)(t);\n}\nexport { toPrimitive as default };","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nimport Fragment from 'flarum/common/Fragment';\nexport default class AutocompleteDropdown extends Fragment {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"items\", []);\n _defineProperty(this, \"active\", false);\n _defineProperty(this, \"index\", 0);\n _defineProperty(this, \"keyWasJustPressed\", false);\n }\n view() {\n return m(\"ul\", {\n className: \"Dropdown-menu MentionsDropdown\"\n }, this.items.map(item => m(\"li\", null, item)));\n }\n show(left, top) {\n this.$().show().css({\n left: left + 'px',\n top: top + 'px'\n });\n this.active = true;\n }\n hide() {\n this.$().hide();\n this.active = false;\n }\n navigate(delta) {\n this.keyWasJustPressed = true;\n this.setIndex(this.index + delta, true);\n clearTimeout(this.keyWasJustPressedTimeout);\n this.keyWasJustPressedTimeout = setTimeout(() => this.keyWasJustPressed = false, 500);\n }\n complete() {\n this.$('li').eq(this.index).find('button').click();\n }\n setIndex(index, scrollToItem) {\n if (this.keyWasJustPressed && !scrollToItem) return;\n const $dropdown = this.$();\n const $items = $dropdown.find('li');\n let rangedIndex = index;\n if (rangedIndex < 0) {\n rangedIndex = $items.length - 1;\n } else if (rangedIndex >= $items.length) {\n rangedIndex = 0;\n }\n this.index = rangedIndex;\n const $item = $items.removeClass('active').eq(rangedIndex).addClass('active');\n if (scrollToItem) {\n const dropdownScroll = $dropdown.scrollTop();\n const dropdownTop = $dropdown.offset().top;\n const dropdownBottom = dropdownTop + $dropdown.outerHeight();\n const itemTop = $item.offset().top;\n const itemBottom = itemTop + $item.outerHeight();\n let scrollTop;\n if (itemTop < dropdownTop) {\n scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt($dropdown.css('padding-top'), 10);\n } else if (itemBottom > dropdownBottom) {\n scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt($dropdown.css('padding-bottom'), 10);\n }\n if (typeof scrollTop !== 'undefined') {\n $dropdown.stop(true).animate({\n scrollTop\n }, 100);\n }\n }\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/fragments/AutocompleteDropdown', AutocompleteDropdown);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/Component');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/utils/classList');","import Component from 'flarum/common/Component';\nimport classList from 'flarum/common/utils/classList';\nexport default class MentionsDropdownItem extends Component {\n view(vnode) {\n const {\n mentionable,\n ...attrs\n } = this.attrs;\n const className = classList('MentionsDropdownItem', 'PostPreview', `MentionsDropdown-${mentionable.type()}`);\n return m(\"button\", Object.assign({\n className: className\n }, attrs), m(\"span\", {\n className: \"PostPreview-content\"\n }, vnode.children));\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/components/MentionsDropdownItem', MentionsDropdownItem);","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nimport MentionsDropdownItem from '../components/MentionsDropdownItem';\nexport default class MentionableModels {\n constructor(dropdownItemAttrs) {\n _defineProperty(this, \"mentionables\", void 0);\n /**\n * We store models returned from an API here to preserve order in which they are returned\n * This prevents the list jumping around while models are returned.\n * We also use a hashmap for model IDs to provide O(1) lookup for the users already in the list.\n */\n _defineProperty(this, \"results\", {});\n _defineProperty(this, \"typed\", null);\n _defineProperty(this, \"searched\", []);\n _defineProperty(this, \"dropdownItemAttrs\", {});\n /**\n * Don't send API calls searching for models until at least 2 characters have been typed.\n * This focuses the mention results on models already loaded.\n */\n _defineProperty(this, \"search\", async () => {\n if (!this.typed || this.typed.length <= 1) return;\n const typedLower = this.typed.toLowerCase();\n if (this.searched.includes(typedLower)) return;\n for (const mentionable of this.mentionables) {\n for (const model of await mentionable.search(typedLower)) {\n if (!this.results[mentionable.type()].has(model.id())) {\n this.results[mentionable.type()].set(model.id(), model);\n }\n }\n }\n this.searched.push(typedLower);\n return Promise.resolve();\n });\n this.dropdownItemAttrs = dropdownItemAttrs;\n }\n init(mentionables) {\n this.typed = null;\n this.mentionables = mentionables;\n for (const mentionable of this.mentionables) {\n this.results[mentionable.type()] = new Map(mentionable.initialResults().map(result => [result.id(), result]));\n }\n }\n matches(mentionable, model) {\n return mentionable.matches(model, this.typed?.toLowerCase() || '');\n }\n makeSuggestion(mentionable, model) {\n const content = mentionable.suggestion(model, this.typed);\n const replacement = mentionable.replacement(model);\n const {\n onclick,\n ...attrs\n } = this.dropdownItemAttrs;\n return m(MentionsDropdownItem, Object.assign({\n mentionable: mentionable,\n onclick: () => onclick(replacement)\n }, attrs), content);\n }\n buildSuggestions() {\n const suggestions = [];\n for (const mentionable of this.mentionables) {\n if (!mentionable.enabled()) continue;\n let matches = Array.from(this.results[mentionable.type()].values()).filter(model => this.matches(mentionable, model));\n const max = mentionable.maxStoreMatchedResults();\n if (max) matches = matches.splice(0, max);\n for (const model of matches) {\n const dropdownItem = this.makeSuggestion(mentionable, model);\n suggestions.push(dropdownItem);\n }\n }\n return suggestions;\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/mentionables/MentionableModels', MentionableModels);","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nexport default class MentionFormat {\n constructor() {\n _defineProperty(this, \"instances\", void 0);\n _defineProperty(this, \"mentionables\", void 0);\n _defineProperty(this, \"extendable\", void 0);\n }\n makeMentionables() {\n return this.instances ?? (this.instances = this.mentionables.map(Mentionable => new Mentionable(this)));\n }\n getMentionable(type) {\n return this.makeMentionables().find(mentionable => mentionable.type() === type) ?? null;\n }\n extend(mentionable) {\n if (!this.extendable) throw new Error('This mention format does not allow extending.');\n this.mentionables.push(mentionable);\n }\n\n /**\n * Picks the term to search in the API from the typed text.\n * @example:\n * * Full text = `Hello @\"John D`\n * * Typed text = `\"John D`\n * * Query = `John D`\n */\n}\nflarum.reg.add('flarum-mentions', 'forum/mentionables/formats/MentionFormat', MentionFormat);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Avatar');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/helpers/highlight');","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nexport default class MentionableModel {\n constructor(format) {\n _defineProperty(this, \"format\", void 0);\n this.format = format;\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/mentionables/MentionableModel', MentionableModel);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/utils/extractText');","import app from 'flarum/forum/app';\nimport extractText from 'flarum/common/utils/extractText';\n\n/**\n * Whether to use the old mentions format.\n *\n * `'@username'` or `'@\"Display name\"'`\n */\nexport const shouldUseOldFormat = () => app.forum.attribute('allowUsernameMentionFormat') || false;\nconst getDeletedUserText = () => extractText(app.translator.trans('core.lib.username.deleted_text'));\n\n/**\n * Fetches a user's username or display name.\n *\n * Chooses based on the format option set in the admin settings page.\n *\n * @param user An instance of the User model to fetch the username for\n * @param useDisplayName If `true`, uses `user.displayName()`, otherwise, uses `user.username()`\n */\nexport default function getCleanDisplayName(user, useDisplayName) {\n if (useDisplayName === void 0) {\n useDisplayName = true;\n }\n if (!user) return getDeletedUserText().replace(/\"#[a-z]{0,3}[0-9]+/, '_');\n const text = (useDisplayName ? user.displayName() : user.username()) || getDeletedUserText();\n return text.replace(/\"#[a-z]{0,3}[0-9]+/, '_');\n}\nflarum.reg.add('flarum-mentions', 'forum/utils/getCleanDisplayName', getCleanDisplayName);","import app from 'flarum/forum/app';\nimport usernameHelper from 'flarum/common/helpers/username';\nimport Avatar from 'flarum/common/components/Avatar';\nimport highlight from 'flarum/common/helpers/highlight';\nimport MentionableModel from './MentionableModel';\nimport getCleanDisplayName, { shouldUseOldFormat } from '../utils/getCleanDisplayName';\nexport default class UserMention extends MentionableModel {\n type() {\n return 'user';\n }\n initialResults() {\n return Array.from(app.store.all('users'));\n }\n\n /**\n * Automatically determines which mention syntax to be used based on the option in the\n * admin dashboard. Also performs display name clean-up automatically.\n *\n * @\"Display name\"#UserID or `@username`\n *\n * @example
New display name syntax
\n * // '@\"user\"#1'\n * forUser(User) // User is ID 1, display name is 'User'\n *\n * @example
Using old syntax
\n * // '@username'\n * forUser(user) // User's username is 'username'\n */\n replacement(user) {\n if (shouldUseOldFormat()) {\n const cleanText = getCleanDisplayName(user, false);\n return this.format.format(cleanText);\n }\n const cleanText = getCleanDisplayName(user);\n return this.format.format(cleanText, '', user.id());\n }\n suggestion(model, typed) {\n const username = usernameHelper(model, name => highlight(name, typed));\n return m('[', null, m(Avatar, {\n user: model\n }), username);\n }\n matches(model, typed) {\n if (!typed) return false;\n const names = [model.username(), model.displayName()];\n return names.some(name => name.toLowerCase().substr(0, typed.length) === typed);\n }\n maxStoreMatchedResults() {\n return null;\n }\n async search(typed) {\n return await app.store.find('users', {\n filter: {\n q: typed\n },\n page: {\n limit: 5\n }\n });\n }\n enabled() {\n return true;\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/mentionables/UserMention', UserMention);","import app from 'flarum/forum/app';\nimport MentionableModel from './MentionableModel';\nimport usernameHelper from 'flarum/common/helpers/username';\nimport Avatar from 'flarum/common/components/Avatar';\nimport highlight from 'flarum/common/helpers/highlight';\nimport { truncate } from 'flarum/common/utils/string';\nimport getCleanDisplayName from '../utils/getCleanDisplayName';\nexport default class PostMention extends MentionableModel {\n type() {\n return 'post';\n }\n\n /**\n * If the user is replying to a discussion, or if they are editing a\n * post, then we can suggest other posts in the discussion to mention.\n * We will add the 5 most recent comments in the discussion which\n * match any username characters that have been typed.\n */\n initialResults() {\n const EditPostComposer = flarum.reg.checkModule('core', 'forum/components/EditPostComposer');\n const ReplyComposer = flarum.reg.checkModule('core', 'forum/components/ReplyComposer');\n if ((!ReplyComposer || !app.composer.bodyMatches(ReplyComposer)) && (!EditPostComposer || !app.composer.bodyMatches(EditPostComposer))) {\n return [];\n }\n\n // @ts-ignore\n const composerAttrs = app.composer.body.attrs;\n const composerPost = composerAttrs.post;\n const discussion = composerPost && composerPost.discussion() || composerAttrs.discussion;\n return discussion.posts()\n // Filter to only comment posts, and replies before this message\n .filter(post => post && post.contentType() === 'comment' && (!composerPost || post.number() < composerPost.number()))\n // Sort by new to old\n .sort((a, b) => b.createdAt().getTime() - a.createdAt().getTime());\n }\n\n /**\n * Generates the syntax for mentioning of a post. Also cleans up the display name.\n *\n * @example
Post mention
\n * // '@\"User\"#p13'\n * // @\"Display name\"#pPostID\n * forPostMention(user, 13) // User display name is 'User', post ID is 13\n */\n replacement(post) {\n const user = post.user();\n const cleanText = getCleanDisplayName(user);\n return this.format.format(cleanText, 'p', post.id());\n }\n suggestion(model, typed) {\n const user = model.user() || null;\n const username = usernameHelper(user, name => highlight(name, typed));\n return m('[', null, m(Avatar, {\n user: user\n }), username, [app.translator.trans('flarum-mentions.forum.composer.reply_to_post_text', {\n number: model.number()\n }), ' — ', truncate(model.contentPlain() ?? '', 200)]);\n }\n matches(model, typed) {\n const user = model.user();\n const userMentionable = app.mentionFormats.mentionable('user');\n return !typed || user && userMentionable.matches(user, typed);\n }\n maxStoreMatchedResults() {\n return 5;\n }\n\n /**\n * Post mention suggestions are only offered from current discussion posts.\n */\n search(typed) {\n return Promise.resolve([]);\n }\n enabled() {\n return true;\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/mentionables/PostMention', PostMention);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/models/Group');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Badge');","import app from 'flarum/forum/app';\nimport Group from 'flarum/common/models/Group';\nimport MentionableModel from './MentionableModel';\nimport Badge from 'flarum/common/components/Badge';\nimport highlight from 'flarum/common/helpers/highlight';\nexport default class GroupMention extends MentionableModel {\n type() {\n return 'group';\n }\n initialResults() {\n return Array.from(app.store.all('groups').filter(g => {\n return g.id() !== Group.GUEST_ID && g.id() !== Group.MEMBER_ID;\n }));\n }\n\n /**\n * Generates the mention syntax for a group mention.\n *\n * @\"Name Plural\"#gGroupID\n *\n * @example
Group mention
\n * // '@\"Mods\"#g4'\n * forGroup(group) // Group display name is 'Mods', group ID is 4\n */\n replacement(group) {\n return this.format.format(group.namePlural(), 'g', group.id());\n }\n suggestion(model, typed) {\n let groupName = model.namePlural();\n if (typed) {\n groupName = highlight(groupName, typed);\n }\n return m('[', null, m(Badge, {\n className: `Avatar Badge Badge--group--${model.id()} Badge-icon`,\n color: model.color(),\n type: \"group\",\n icon: model.icon()\n }), m(\"span\", {\n className: \"username\"\n }, groupName));\n }\n matches(model, typed) {\n if (!typed) return false;\n const names = [model.namePlural().toLowerCase(), model.nameSingular().toLowerCase()];\n return names.some(name => name.toLowerCase().substr(0, typed.length) === typed);\n }\n maxStoreMatchedResults() {\n return null;\n }\n\n /**\n * All groups are already loaded, so we don't need to search for them.\n */\n search(typed) {\n return Promise.resolve([]);\n }\n enabled() {\n return app.session?.user?.canMentionGroups() ?? false;\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/mentionables/GroupMention', GroupMention);","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nimport MentionFormat from './MentionFormat';\nimport UserMention from '../UserMention';\nimport PostMention from '../PostMention';\nimport GroupMention from '../GroupMention';\nexport default class AtMentionFormat extends MentionFormat {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"mentionables\", [UserMention, PostMention, GroupMention]);\n _defineProperty(this, \"extendable\", true);\n }\n trigger() {\n return '@';\n }\n queryFromTyped(typed) {\n const matchTyped = typed.match(/^[\"“]?((?:(?!\"#).)+)$/);\n return matchTyped ? matchTyped[1] : null;\n }\n format(name, char, id) {\n if (char === void 0) {\n char = '';\n }\n if (id === void 0) {\n id = null;\n }\n return {\n simple: `@${name}`,\n safe: `@\"${name}\"#${char}${id}`\n }[id ? 'safe' : 'simple'];\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/mentionables/formats/AtMentionFormat', AtMentionFormat);","import app from 'flarum/forum/app';\nimport Badge from 'flarum/common/components/Badge';\nimport highlight from 'flarum/common/helpers/highlight';\nimport MentionableModel from './MentionableModel';\nexport default class TagMention extends MentionableModel {\n type() {\n return 'tag';\n }\n initialResults() {\n return Array.from(app.store.all('tags'));\n }\n\n /**\n * Generates the mention syntax for a tag mention.\n *\n * ~tagSlug\n *\n * @example
Tag mention
\n * // ~general\n * forTag(tag) // Tag display name is 'Tag', tag ID is 5\n */\n replacement(tag) {\n return this.format.format(tag.slug());\n }\n matches(model, typed) {\n if (!typed) return false;\n const names = [model.name().toLowerCase()];\n return names.some(name => name.toLowerCase().substr(0, typed.length) === typed);\n }\n maxStoreMatchedResults() {\n return null;\n }\n async search(typed) {\n return await app.store.find('tags', {\n filter: {\n q: typed\n },\n page: {\n limit: 5\n }\n });\n }\n suggestion(model, typed) {\n let tagName = model.name();\n if (typed) {\n tagName = highlight(tagName, typed);\n }\n return m('[', null, m(Badge, {\n className: \"Avatar\",\n icon: model.icon(),\n color: model.color(),\n type: \"tag\"\n }), m(\"span\", {\n className: \"username\"\n }, tagName));\n }\n enabled() {\n return 'flarum-tags' in flarum.extensions;\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/mentionables/TagMention', TagMention);","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nimport MentionFormat from './MentionFormat';\nimport TagMention from '../TagMention';\nexport default class HashMentionFormat extends MentionFormat {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"mentionables\", [TagMention]);\n _defineProperty(this, \"extendable\", true);\n }\n trigger() {\n return '#';\n }\n queryFromTyped(typed) {\n const matchTyped = typed.match(/^[-_\\p{L}\\p{N}\\p{M}]+$/giu);\n return matchTyped ? matchTyped[0] : null;\n }\n format(slug) {\n return `#${slug}`;\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/mentionables/formats/HashMentionFormat', HashMentionFormat);","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nimport AtMentionFormat from './AtMentionFormat';\nimport HashMentionFormat from './HashMentionFormat';\nexport default class MentionFormats {\n constructor() {\n _defineProperty(this, \"formats\", [new AtMentionFormat(), new HashMentionFormat()]);\n }\n get(symbol) {\n return this.formats.find(f => f.trigger() === symbol) ?? null;\n }\n mentionable(type) {\n for (const format of this.formats) {\n const mentionable = format.getMentionable(type);\n if (mentionable) return mentionable;\n }\n return null;\n }\n extend(format) {\n this.formats.push(new format());\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/mentionables/formats/MentionFormats', MentionFormats);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/components/UserPage');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/LinkButton');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/extenders');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/models/Post');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/models/User');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/components/PostsUserPage');","import PostsUserPage from 'flarum/forum/components/PostsUserPage';\n/**\n * The `MentionsUserPage` component shows post which user Mentioned at\n */\nexport default class MentionsUserPage extends PostsUserPage {\n params(user) {\n return {\n filter: {\n type: 'comment',\n mentioned: user.id()\n }\n };\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/components/MentionsUserPage', MentionsUserPage);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/components/Notification');","import app from 'flarum/forum/app';\nimport Notification from 'flarum/forum/components/Notification';\nimport { truncate } from 'flarum/common/utils/string';\nexport default class PostMentionedNotification extends Notification {\n icon() {\n return 'fas fa-reply';\n }\n href() {\n const notification = this.attrs.notification;\n const post = notification.subject();\n const content = notification.content();\n return app.route.discussion(post.discussion(), content && content.replyNumber);\n }\n content() {\n const notification = this.attrs.notification;\n const user = notification.fromUser();\n return app.translator.trans('flarum-mentions.forum.notifications.post_mentioned_text', {\n user,\n count: 1\n });\n }\n excerpt() {\n return truncate(this.attrs.notification.subject().contentPlain() || '', 200);\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/components/PostMentionedNotification', PostMentionedNotification);","import app from 'flarum/forum/app';\nimport Notification from 'flarum/forum/components/Notification';\nimport { truncate } from 'flarum/common/utils/string';\nexport default class UserMentionedNotification extends Notification {\n icon() {\n return 'fas fa-at';\n }\n href() {\n const post = this.attrs.notification.subject();\n return app.route.discussion(post.discussion(), post.number());\n }\n content() {\n const user = this.attrs.notification.fromUser();\n return app.translator.trans('flarum-mentions.forum.notifications.user_mentioned_text', {\n user\n });\n }\n excerpt() {\n return truncate(this.attrs.notification.subject().contentPlain(), 200);\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/components/UserMentionedNotification', UserMentionedNotification);","import app from 'flarum/forum/app';\nimport Notification from 'flarum/forum/components/Notification';\nimport { truncate } from 'flarum/common/utils/string';\nexport default class GroupMentionedNotification extends Notification {\n icon() {\n return 'fas fa-at';\n }\n href() {\n const post = this.attrs.notification.subject();\n return app.route.discussion(post.discussion(), post.number());\n }\n content() {\n const user = this.attrs.notification.fromUser();\n return app.translator.trans('flarum-mentions.forum.notifications.group_mentioned_text', {\n user\n });\n }\n excerpt() {\n return truncate(this.attrs.notification.subject().contentPlain(), 200);\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/components/GroupMentionedNotification', GroupMentionedNotification);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/query/IGambit');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/app');","import { KeyValueGambit } from 'flarum/common/query/IGambit';\nimport app from 'flarum/common/app';\nexport default class MentionedGambit extends KeyValueGambit {\n key() {\n return app.translator.trans('flarum-mentions.lib.gambits.posts.mentioned.key', {}, true);\n }\n hint() {\n return app.translator.trans('flarum-mentions.lib.gambits.posts.mentioned.hint', {}, true);\n }\n filterKey() {\n return 'mentioned';\n }\n}\nflarum.reg.add('flarum-mentions', 'common/query/posts/MentionedGambit', MentionedGambit);","import Extend from 'flarum/common/extenders';\nimport MentionedGambit from './query/posts/MentionedGambit';\nexport default [new Extend.Search() //\n.gambit('posts', MentionedGambit)];","import Extend from 'flarum/common/extenders';\nimport Post from 'flarum/common/models/Post';\nimport User from 'flarum/common/models/User';\nimport MentionsUserPage from './components/MentionsUserPage';\nimport PostMentionedNotification from './components/PostMentionedNotification';\nimport UserMentionedNotification from './components/UserMentionedNotification';\nimport GroupMentionedNotification from './components/GroupMentionedNotification';\nimport commonExtend from '../common/extend';\nexport default [...commonExtend, new Extend.Routes() //\n.add('user.mentions', '/u/:username/mentions', MentionsUserPage), new Extend.Model(Post) //\n.hasMany('mentionedBy').attribute('mentionedByCount'), new Extend.Notification() //\n.add('postMentioned', PostMentionedNotification).add('userMentioned', UserMentionedNotification).add('groupMentioned', GroupMentionedNotification), new Extend.Model(User) //\n.attribute('canMentionGroups')];","import app from 'flarum/forum/app';\nimport username from 'flarum/common/helpers/username';\nimport extractText from 'flarum/common/utils/extractText';\nexport function filterUserMentions(tag) {\n let user;\n if (app.forum.attribute('allowUsernameMentionFormat') && tag.hasAttribute('username')) user = app.store.getBy('users', 'username', tag.getAttribute('username'));else if (tag.hasAttribute('id')) user = app.store.getById('users', tag.getAttribute('id'));\n if (user) {\n tag.setAttribute('id', user.id());\n tag.setAttribute('slug', user.slug());\n tag.setAttribute('displayname', extractText(username(user)));\n return true;\n }\n tag.invalidate();\n}\nexport function postFilterUserMentions(tag) {\n tag.setAttribute('deleted', false);\n}\nexport function filterPostMentions(tag) {\n const post = app.store.getById('posts', tag.getAttribute('id'));\n if (post) {\n tag.setAttribute('discussionid', post.discussion().id());\n tag.setAttribute('number', post.number());\n tag.setAttribute('displayname', extractText(username(post.user())));\n return true;\n }\n}\nexport function postFilterPostMentions(tag) {\n tag.setAttribute('deleted', false);\n}\nexport function filterGroupMentions(tag) {\n if (app.session?.user?.canMentionGroups()) {\n const group = app.store.getById('groups', tag.getAttribute('id'));\n if (group) {\n tag.setAttribute('groupname', extractText(group.namePlural()));\n return true;\n }\n }\n tag.invalidate();\n}\nexport function postFilterGroupMentions(tag) {\n if (app.session?.user?.canMentionGroups()) {\n const group = app.store.getById('groups', tag.getAttribute('id'));\n tag.setAttribute('color', group.color());\n tag.setAttribute('icon', group.icon());\n tag.setAttribute('deleted', false);\n }\n}\nexport function filterTagMentions(tag) {\n if ('flarum-tags' in flarum.extensions) {\n const model = app.store.getBy('tags', 'slug', tag.getAttribute('slug'));\n if (model) {\n tag.setAttribute('id', model.id());\n tag.setAttribute('tagname', model.name());\n return true;\n }\n }\n tag.invalidate();\n}\nexport function postFilterTagMentions(tag) {\n if ('flarum-tags' in flarum.extensions) {\n const model = app.store.getBy('tags', 'slug', tag.getAttribute('slug'));\n tag.setAttribute('icon', model.icon());\n tag.setAttribute('color', model.color());\n tag.setAttribute('deleted', false);\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/utils/textFormatter', { filterUserMentions: filterUserMentions,postFilterUserMentions: postFilterUserMentions,filterPostMentions: filterPostMentions,postFilterPostMentions: postFilterPostMentions,filterGroupMentions: filterGroupMentions,postFilterGroupMentions: postFilterGroupMentions,filterTagMentions: filterTagMentions,postFilterTagMentions: postFilterTagMentions, });","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nexport default class Mentionables {\n constructor() {\n _defineProperty(this, \"formats\", []);\n _defineProperty(this, \"mentionables\", {});\n }\n /**\n * Register a new mention format.\n * Must extend MentionFormat and have a unique unused trigger symbol.\n */\n format(format) {\n this.formats.push(format);\n return this;\n }\n\n /**\n * Register a new mentionable model to a mention format.\n * Only works if the format has already been registered,\n * and the format allows using multiple mentionables.\n *\n * @param symbol The trigger symbol of the format to extend (ex: @).\n * @param mentionable The mentionable instance to register.\n * Must extend MentionableModel.\n */\n mentionable(symbol, mentionable) {\n if (!this.mentionables[symbol]) {\n this.mentionables[symbol] = [];\n }\n this.mentionables[symbol].push(mentionable);\n return this;\n }\n extend(app) {\n for (const format of this.formats) {\n app.mentionFormats.extend(format);\n }\n for (const symbol in this.mentionables) {\n const format = app.mentionFormats.get(symbol);\n if (!format) continue;\n for (const mentionable of this.mentionables[symbol]) {\n format.extend(mentionable);\n }\n }\n }\n}\nflarum.reg.add('flarum-mentions', 'forum/extenders/Mentionables', Mentionables);","import { extend, override } from 'flarum/common/extend';\nimport app from 'flarum/forum/app';\nimport { getPlainContent } from 'flarum/common/utils/string';\nimport textContrastClass from 'flarum/common/helpers/textContrastClass';\nimport Post from 'flarum/forum/components/Post';\nimport addPostMentionPreviews from './addPostMentionPreviews';\nimport addMentionedByList from './addMentionedByList';\nimport addPostReplyAction from './addPostReplyAction';\nimport addPostQuoteButton from './addPostQuoteButton';\nimport addComposerAutocomplete from './addComposerAutocomplete';\nimport MentionFormats from './mentionables/formats/MentionFormats';\nimport UserPage from 'flarum/forum/components/UserPage';\nimport LinkButton from 'flarum/common/components/LinkButton';\napp.mentionFormats = new MentionFormats();\nexport { default as extend } from './extend';\napp.initializers.add('flarum-mentions', () => {\n // For every mention of a post inside a post's content, set up a hover handler\n // that shows a preview of the mentioned post.\n addPostMentionPreviews();\n\n // In the footer of each post, show information about who has replied (i.e.\n // who the post has been mentioned by).\n addMentionedByList();\n\n // Add a 'reply' control to the footer of each post. When clicked, it will\n // open up the composer and add a post mention to its contents.\n addPostReplyAction();\n\n // Show a Quote button when Post text is selected\n addPostQuoteButton();\n\n // After typing '@' in the composer, show a dropdown suggesting a bunch of\n // posts or users that the user could mention.\n addComposerAutocomplete();\n\n // Add notification preferences.\n extend('flarum/forum/components/NotificationGrid', 'notificationTypes', function (items) {\n items.add('postMentioned', {\n name: 'postMentioned',\n icon: 'fas fa-reply',\n label: app.translator.trans('flarum-mentions.forum.settings.notify_post_mentioned_label')\n });\n items.add('userMentioned', {\n name: 'userMentioned',\n icon: 'fas fa-at',\n label: app.translator.trans('flarum-mentions.forum.settings.notify_user_mentioned_label')\n });\n items.add('groupMentioned', {\n name: 'groupMentioned',\n icon: 'fas fa-at',\n label: app.translator.trans('flarum-mentions.forum.settings.notify_group_mentioned_label')\n });\n });\n\n // Add mentions tab in user profile\n extend(UserPage.prototype, 'navItems', function (items) {\n const user = this.user;\n items.add('mentions', m(LinkButton, {\n href: app.route('user.mentions', {\n username: user.slug()\n }),\n name: \"mentions\",\n icon: \"fas fa-at\"\n }, app.translator.trans('flarum-mentions.forum.user.mentions_link')), 80);\n });\n\n // Remove post mentions when rendering post previews.\n getPlainContent.removeSelectors.push('a.PostMention');\n\n // Apply color contrast fix on group mentions.\n extend(Post.prototype, 'oncreate', function () {\n this.$('.GroupMention--colored, .TagMention--colored').each(function () {\n this.classList.add(textContrastClass(getComputedStyle(this).getPropertyValue('--color')));\n });\n });\n\n // Auto scope the search to the current user mentioned posts.\n override('flarum/forum/components/SearchModal', 'defaultActiveSource', function (original) {\n const orig = original();\n if (!orig && app.current.data.routeName && app.current.data.routeName.includes('user.mentions') && app.current.data.user) {\n return 'posts';\n }\n return orig;\n });\n extend('flarum/forum/components/SearchModal', 'defaultFilters', function (filters) {\n if (app.current.data.routeName && app.current.data.routeName.includes('user.mentions') && app.current.data.user) {\n filters.posts.mentioned = app.current.data.user.username();\n }\n });\n});\nexport * from './utils/textFormatter';\nimport './forum';","import { extend } from 'flarum/common/extend';\nimport CommentPost from 'flarum/forum/components/CommentPost';\nimport PostPreview from 'flarum/forum/components/PostPreview';\nimport LoadingIndicator from 'flarum/common/components/LoadingIndicator';\nexport default function addPostMentionPreviews() {\n function addPreviews() {\n const contentHtml = this.attrs.post.contentHtml();\n if (contentHtml === this.oldPostContentHtml || this.isEditing()) return;\n this.oldPostContentHtml = contentHtml;\n const parentPost = this.attrs.post;\n const $parentPost = this.$();\n this.$().on('click', '.UserMention:not(.UserMention--deleted), .PostMention:not(.PostMention--deleted), .TagMention:not(.TagMention--deleted)', function (e) {\n m.route.set(this.getAttribute('href'));\n e.preventDefault();\n });\n this.$('.PostMention:not(.PostMention--deleted)').each(function () {\n const $this = $(this);\n const id = $this.data('id');\n let timeout;\n\n // Wrap the mention link in a wrapper element so that we can insert a\n // preview popup as its sibling and relatively position it.\n const $preview = $('
');\n $parentPost.append($preview);\n const getPostElement = () => {\n return $(`.PostStream-item[data-id=\"${id}\"]`);\n };\n const showPreview = () => {\n // When the user hovers their mouse over the mention, look for the\n // post that it's referring to in the stream, and determine if it's\n // in the viewport. If it is, we will \"pulsate\" it.\n const $post = getPostElement();\n let visible = false;\n if ($post.length) {\n const top = $post.offset().top;\n const scrollTop = window.pageYOffset;\n if (top > scrollTop && top + $post.height() < scrollTop + $(window).height()) {\n $post.addClass('pulsate');\n visible = true;\n }\n }\n\n // Otherwise, we will show a popup preview of the post. If the post\n // hasn't yet been loaded, we will need to do that.\n if (!visible) {\n // Position the preview so that it appears above the mention.\n // (The offsetParent should be .Post-body.)\n const positionPreview = () => {\n const previewHeight = $preview.outerHeight(true);\n let offset = 0;\n\n // If the preview goes off the top of the viewport, reposition it to\n // be below the mention.\n if ($this.offset().top - previewHeight < $(window).scrollTop() + $('#header').outerHeight()) {\n offset += $this.outerHeight(true);\n } else {\n offset -= previewHeight;\n }\n $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());\n };\n const showPost = post => {\n const discussion = post.discussion();\n m.render($preview[0], [discussion !== parentPost.discussion() && m(\"li\", null, m(\"span\", {\n className: \"PostMention-preview-discussion\"\n }, discussion.title())), m(\"li\", null, m(PostPreview, {\n post: post\n }))]);\n positionPreview();\n };\n const post = app.store.getById('posts', id);\n if (post && post.discussion()) {\n showPost(post);\n } else {\n m.render($preview[0], m(LoadingIndicator, null));\n app.store.find('posts', id).then(showPost);\n positionPreview();\n }\n setTimeout(() => $preview.off('transitionend').addClass('in'));\n }\n };\n const hidePreview = () => {\n getPostElement().removeClass('pulsate');\n if ($preview.hasClass('in')) {\n $preview.removeClass('in').one('transitionend', () => $preview.hide());\n }\n };\n\n // On a touch (mobile) device we cannot hover the link to reveal the preview.\n // Instead we cancel the navigation so that a click reveals the preview.\n // Users can then click on the preview to go to the post if desired.\n $this.on('touchend', e => {\n if (e.cancelable) {\n e.preventDefault();\n }\n });\n $this.add($preview).hover(() => {\n clearTimeout(timeout);\n timeout = setTimeout(showPreview, 250);\n }, () => {\n clearTimeout(timeout);\n getPostElement().removeClass('pulsate');\n timeout = setTimeout(hidePreview, 250);\n }).on('touchend', e => {\n showPreview();\n e.stopPropagation();\n });\n $(document).on('touchend', hidePreview);\n });\n }\n extend(CommentPost.prototype, 'oncreate', addPreviews);\n extend(CommentPost.prototype, 'onupdate', addPreviews);\n}","import app from 'flarum/forum/app';\nimport { extend } from 'flarum/common/extend';\nimport CommentPost from 'flarum/forum/components/CommentPost';\nimport Link from 'flarum/common/components/Link';\nimport PostPreview from 'flarum/forum/components/PostPreview';\nimport punctuateSeries from 'flarum/common/helpers/punctuateSeries';\nimport username from 'flarum/common/helpers/username';\nimport Icon from 'flarum/common/components/Icon';\nimport Button from 'flarum/common/components/Button';\nimport MentionedByModal from './components/MentionedByModal';\nexport default function addMentionedByList() {\n function hidePreview() {\n this.$('.Post-mentionedBy-preview').removeClass('in').one('transitionend', function () {\n $(this).hide();\n });\n }\n extend(CommentPost.prototype, 'oncreate', function () {\n let timeout;\n const post = this.attrs.post;\n const replies = post.mentionedBy();\n if (replies && replies.length) {\n const $preview = $('
');\n this.$().append($preview);\n const $parentPost = this.$();\n const $this = this.$('.Post-mentionedBy');\n const showPreview = () => {\n if (!$preview.hasClass('in') && $preview.is(':visible')) return;\n\n // When the user hovers their mouse over the list of people who have\n // replied to the post, render a list of reply previews into a\n // popup.\n m.render($preview[0], m('[', null, replies.map(reply => m(\"li\", {\n \"data-number\": reply.number()\n }, m(PostPreview, {\n post: reply,\n onclick: hidePreview.bind(this)\n }))), replies.length < post.mentionedByCount() && m(\"li\", {\n className: \"Post-mentionedBy-preview-more\"\n }, m(Button, {\n className: \"PostPreview Button\",\n onclick: () => {\n hidePreview.call(this);\n app.modal.show(MentionedByModal, {\n post\n });\n }\n }, m(\"span\", {\n className: \"PostPreview-content\"\n }, m(\"span\", {\n className: \"PostPreview-badge Avatar\"\n }, m(Icon, {\n name: 'fas fa-reply-all'\n })), m(\"span\", null, app.translator.trans('flarum-mentions.forum.post.mentioned_by_more_text', {\n count: post.mentionedByCount() - replies.length\n })))))));\n $preview.show().css('top', $this.offset().top - $parentPost.offset().top + $this.outerHeight(true)).css('left', $this.offsetParent().offset().left - $parentPost.offset().left).css('max-width', $parentPost.width());\n setTimeout(() => $preview.off('transitionend').addClass('in'));\n };\n $this.add($preview).hover(() => {\n clearTimeout(timeout);\n timeout = setTimeout(showPreview, 250);\n }, () => {\n clearTimeout(timeout);\n timeout = setTimeout(hidePreview, 250);\n });\n\n // Whenever the user hovers their mouse over a particular name in the\n // list of repliers, highlight the corresponding post in the preview\n // popup.\n this.$().find('.Post-mentionedBy-summary a').hover(function () {\n $preview.find('[data-number=\"' + $(this).data('number') + '\"]').addClass('active');\n }, function () {\n $preview.find('[data-number]').removeClass('active');\n });\n }\n });\n extend(CommentPost.prototype, 'footerItems', function (items) {\n const post = this.attrs.post;\n const replies = post.mentionedBy();\n if (replies && replies.length) {\n const users = [];\n const repliers = replies.sort(reply => reply.user() === app.session.user ? -1 : 0).filter(reply => {\n const user = reply.user();\n if (users.indexOf(user) === -1) {\n users.push(user);\n return true;\n }\n });\n const limit = 4;\n const overLimit = post.mentionedByCount() > limit;\n\n // Create a list of unique users who have replied. So even if a user has\n // replied twice, they will only be in this array once.\n const names = repliers.slice(0, overLimit ? limit - 1 : limit).map(reply => {\n const user = reply.user();\n return m(Link, {\n href: app.route.post(reply),\n onclick: hidePreview.bind(this),\n \"data-number\": reply.number()\n }, app.session.user === user ? app.translator.trans('flarum-mentions.forum.post.you_text') : username(user));\n });\n\n // If there are more users that we've run out of room to display, add a \"x\n // others\" name to the end of the list. Clicking on it will display a modal\n // with a full list of names.\n if (overLimit) {\n const count = post.mentionedByCount() - names.length;\n names.push(app.translator.trans('flarum-mentions.forum.post.others_text', {\n count\n }));\n }\n items.add('replies', m(\"div\", {\n className: \"Post-mentionedBy\"\n }, m(\"span\", {\n className: \"Post-mentionedBy-summary\"\n }, m(Icon, {\n name: 'fas fa-reply'\n }), app.translator.trans(`flarum-mentions.forum.post.mentioned_by${repliers[0].user() === app.session.user ? '_self' : ''}_text`, {\n count: names.length,\n users: punctuateSeries(names)\n }))));\n }\n });\n}","import app from 'flarum/forum/app';\nimport { extend } from 'flarum/common/extend';\nimport Button from 'flarum/common/components/Button';\nimport CommentPost from 'flarum/forum/components/CommentPost';\nimport reply from './utils/reply';\nexport default function addPostReplyAction() {\n extend(CommentPost.prototype, 'actionItems', function (items) {\n const post = this.attrs.post;\n if (post.isHidden() || app.session.user && !post.discussion().canReply()) return;\n items.add('reply', m(Button, {\n className: \"Button Button--link\",\n onclick: () => reply(post)\n }, app.translator.trans('flarum-mentions.forum.post.reply_link')));\n });\n}","import app from 'flarum/forum/app';\nimport { extend } from 'flarum/common/extend';\nimport CommentPost from 'flarum/forum/components/CommentPost';\nimport PostQuoteButton from './fragments/PostQuoteButton';\nimport selectedText from './utils/selectedText';\nexport default function addPostQuoteButton() {\n extend(CommentPost.prototype, 'oncreate', function () {\n const post = this.attrs.post;\n if (post.isHidden() || app.session.user && !post.discussion().canReply()) return;\n const $postBody = this.$('.Post-body');\n\n // Wrap the quote button in a wrapper element so that we can render\n // button into it.\n const $container = $('');\n const button = new PostQuoteButton(post);\n const handler = function (e) {\n setTimeout(() => {\n const content = selectedText($postBody);\n if (content) {\n button.content = content;\n m.render($container[0], button.render());\n const rects = window.getSelection().getRangeAt(0).getClientRects();\n const firstRect = rects[0];\n if (e.clientY < firstRect.bottom && e.clientX - firstRect.right < firstRect.left - e.clientX) {\n button.showStart(firstRect.left, firstRect.top);\n } else {\n const lastRect = rects[rects.length - 1];\n button.showEnd(lastRect.right, lastRect.bottom);\n }\n }\n }, 1);\n };\n this.$().after($container).on('mouseup', handler);\n if ('ontouchstart' in window) {\n document.addEventListener('selectionchange', handler, false);\n }\n });\n}","import app from 'flarum/forum/app';\nimport { extend } from 'flarum/common/extend';\nimport TextEditorButton from 'flarum/common/components/TextEditorButton';\nimport KeyboardNavigatable from 'flarum/common/utils/KeyboardNavigatable';\nimport AutocompleteReader from 'flarum/common/utils/AutocompleteReader';\nimport { throttle } from 'flarum/common/utils/throttleDebounce';\nimport AutocompleteDropdown from './fragments/AutocompleteDropdown';\nimport MentionableModels from './mentionables/MentionableModels';\nexport default function addComposerAutocomplete() {\n extend('flarum/common/components/TextEditor', 'onbuild', function () {\n this.mentionsDropdown = new AutocompleteDropdown();\n this.searchMentions = throttle(250, (mentionables, buildSuggestions) => mentionables.search().then(buildSuggestions));\n const $editor = this.$('.TextEditor-editor').wrap('');\n this.navigator = new KeyboardNavigatable();\n this.navigator.when(() => this.mentionsDropdown.active).onUp(() => this.mentionsDropdown.navigate(-1)).onDown(() => this.mentionsDropdown.navigate(1)).onSelect(this.mentionsDropdown.complete.bind(this.mentionsDropdown)).onCancel(this.mentionsDropdown.hide.bind(this.mentionsDropdown)).bindTo($editor);\n $editor.after($(''));\n });\n extend('flarum/common/components/TextEditor', 'buildEditorParams', function (params) {\n let matchTyped;\n const suggestionsInputListener = () => {\n const selection = this.attrs.composer.editor.getSelectionRange();\n const cursor = selection[0];\n if (selection[1] - cursor > 0) return;\n let activeFormat = null;\n const autocompleteReader = new AutocompleteReader(character => !!(activeFormat = app.mentionFormats.get(character)));\n const autocompleting = autocompleteReader.check(this.attrs.composer.editor.getLastNChars(30), cursor, /\\S+/);\n const mentionsDropdown = this.mentionsDropdown;\n let mentionables = new MentionableModels({\n onmouseenter: function () {\n mentionsDropdown.setIndex($(this).parent().index());\n },\n onclick: replacement => {\n this.attrs.composer.editor.replaceBeforeCursor(autocompleting.absoluteStart - 1, replacement + ' ');\n this.mentionsDropdown.hide();\n }\n });\n this.mentionsDropdown.hide();\n this.mentionsDropdown.active = false;\n if (autocompleting) {\n mentionables.init(activeFormat.makeMentionables());\n matchTyped = activeFormat.queryFromTyped(autocompleting.typed);\n if (!matchTyped) return;\n mentionables.typed = matchTyped;\n const buildSuggestions = () => {\n // If the user has started to type a mention,\n // then suggest models matching.\n const suggestions = mentionables.buildSuggestions();\n if (suggestions.length) {\n this.mentionsDropdown.items = suggestions;\n m.render(this.$('.ComposerBody-mentionsDropdownContainer')[0], this.mentionsDropdown.render());\n this.mentionsDropdown.show();\n const coordinates = this.attrs.composer.editor.getCaretCoordinates(autocompleting.absoluteStart);\n const width = this.mentionsDropdown.$().outerWidth();\n const height = this.mentionsDropdown.$().outerHeight();\n const parent = this.mentionsDropdown.$().offsetParent();\n let left = coordinates.left;\n let top = coordinates.top + 15;\n\n // Keep the dropdown inside the editor.\n if (top + height > parent.height()) {\n top = coordinates.top - height - 15;\n }\n if (left + width > parent.width()) {\n left = parent.width() - width;\n }\n\n // Prevent the dropdown from going off screen on mobile\n top = Math.max(-(parent.offset().top - $(document).scrollTop()), top);\n left = Math.max(-parent.offset().left, left);\n this.mentionsDropdown.show(left, top);\n } else {\n this.mentionsDropdown.active = false;\n this.mentionsDropdown.hide();\n }\n };\n this.mentionsDropdown.active = true;\n buildSuggestions();\n this.mentionsDropdown.setIndex(0);\n this.mentionsDropdown.$().scrollTop(0);\n this.searchMentions(mentionables, buildSuggestions);\n }\n };\n params.inputListeners.push(suggestionsInputListener);\n });\n extend('flarum/common/components/TextEditor', 'toolbarItems', function (items) {\n items.add('mention', m(TextEditorButton, {\n onclick: () => this.attrs.composer.editor.insertAtCursor(' @'),\n icon: \"fas fa-at\"\n }, app.translator.trans('flarum-mentions.forum.composer.mention_tooltip')));\n });\n}"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Symbol","toStringTag","value","flarum","reg","MentionedByModalState","constructor","params","page","limit","super","type","add","MentionedByModal","oninit","vnode","this","state","filter","mentionedPost","attrs","post","id","sort","refresh","className","title","trans","content","m","isInitialLoading","getPages","map","items","reply","number","onclick","close","hasNext","loadNext","loading","isLoadingNext","insertMention","composer","quote","Promise","resolve","mention","mentionable","replacement","fields","body","originalContent","cursorPosition","editor","getSelectionRange","preceding","slice","precedingNewlines","length","match","insertAtCursor","Array","join","trim","replace","EditPostComposer","checkModule","bodyMatches","discussion","then","PostQuoteButton","view","name","show","left","top","$this","$","parentOffset","offsetParent","offset","css","hideHandler","hide","bind","document","on","showStart","window","scrollTop","outerHeight","showEnd","right","bottom","outerWidth","off","selectedText","selection","getSelection","isCollapsed","range","getRangeAt","parent","commonAncestorContainer","contains","clone","append","cloneContents","find","replaceWith","alt","src","innerText","href","text","_typeof","iterator","_defineProperty","e","r","t","i","toPrimitive","TypeError","String","toPropertyKey","configurable","writable","AutocompleteDropdown","arguments","item","active","navigate","delta","keyWasJustPressed","setIndex","index","clearTimeout","keyWasJustPressedTimeout","setTimeout","complete","eq","click","scrollToItem","$dropdown","$items","rangedIndex","$item","removeClass","addClass","dropdownScroll","dropdownTop","dropdownBottom","itemTop","itemBottom","parseInt","stop","animate","MentionsDropdownItem","assign","children","MentionableModels","dropdownItemAttrs","async","typed","typedLower","toLowerCase","searched","includes","mentionables","model","search","results","has","set","push","init","Map","initialResults","result","matches","makeSuggestion","suggestion","buildSuggestions","suggestions","enabled","from","values","max","maxStoreMatchedResults","splice","dropdownItem","MentionFormat","makeMentionables","instances","Mentionable","getMentionable","extend","extendable","Error","MentionableModel","format","getDeletedUserText","getCleanDisplayName","user","useDisplayName","displayName","username","UserMention","all","attribute","cleanText","some","substr","q","PostMention","ReplyComposer","composerAttrs","composerPost","posts","contentType","b","createdAt","getTime","truncate","contentPlain","userMentionable","GroupMention","g","group","namePlural","groupName","color","icon","nameSingular","canMentionGroups","AtMentionFormat","trigger","queryFromTyped","matchTyped","char","simple","safe","TagMention","tag","slug","tagName","extensions","HashMentionFormat","MentionFormats","symbol","formats","f","MentionsUserPage","mentioned","PostMentionedNotification","notification","subject","replyNumber","fromUser","count","excerpt","UserMentionedNotification","GroupMentionedNotification","MentionedGambit","KeyValueGambit","hint","filterKey","gambit","hasMany","filterUserMentions","hasAttribute","getBy","getAttribute","getById","setAttribute","invalidate","postFilterUserMentions","filterPostMentions","postFilterPostMentions","filterGroupMentions","postFilterGroupMentions","filterTagMentions","postFilterTagMentions","app","mentionFormats","addPreviews","contentHtml","oldPostContentHtml","isEditing","parentPost","$parentPost","route","preventDefault","each","data","timeout","$preview","getPostElement","showPreview","$post","visible","pageYOffset","height","positionPreview","previewHeight","width","showPost","render","store","hidePreview","hasClass","one","cancelable","hover","stopPropagation","addPostMentionPreviews","replies","mentionedBy","is","mentionedByCount","users","repliers","indexOf","overLimit","names","addMentionedByList","isHidden","canReply","$postBody","$container","button","handler","rects","getClientRects","firstRect","clientY","clientX","lastRect","after","addEventListener","mentionsDropdown","searchMentions","throttle","$editor","wrap","navigator","when","onUp","onDown","onSelect","onCancel","bindTo","inputListeners","cursor","activeFormat","autocompleting","character","check","getLastNChars","onmouseenter","replaceBeforeCursor","absoluteStart","coordinates","getCaretCoordinates","Math","label","getPlainContent","removeSelectors","classList","getComputedStyle","getPropertyValue","override","original","orig","routeName","filters"],"sourceRoot":""}
\ No newline at end of file
diff --git a/extensions/mentions/js/package.json b/extensions/mentions/js/package.json
index 8d95ecfdab..012dec03db 100644
--- a/extensions/mentions/js/package.json
+++ b/extensions/mentions/js/package.json
@@ -5,7 +5,7 @@
"prettier": "@flarum/prettier-config",
"devDependencies": {
"prettier": "^2.5.1",
- "flarum-webpack-config": "^2.0.0",
+ "flarum-webpack-config": "^3.0.0",
"webpack": "^5.76.0",
"webpack-cli": "^4.9.1",
"@flarum/prettier-config": "^1.0.0"
diff --git a/extensions/mentions/js/src/admin/extend.ts b/extensions/mentions/js/src/admin/extend.ts
new file mode 100644
index 0000000000..b42b710315
--- /dev/null
+++ b/extensions/mentions/js/src/admin/extend.ts
@@ -0,0 +1,23 @@
+import Extend from 'flarum/common/extenders';
+import app from 'flarum/admin/app';
+import commonExtend from '../common/extend';
+
+export default [
+ ...commonExtend,
+
+ new Extend.Admin()
+ .setting(() => ({
+ setting: 'flarum-mentions.allow_username_format',
+ type: 'boolean',
+ label: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_label'),
+ help: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_text'),
+ }))
+ .permission(
+ () => ({
+ permission: 'mentionGroups',
+ label: app.translator.trans('flarum-mentions.admin.permissions.mention_groups_label'),
+ icon: 'fas fa-at',
+ }),
+ 'start'
+ ),
+];
diff --git a/extensions/mentions/js/src/admin/index.js b/extensions/mentions/js/src/admin/index.js
deleted file mode 100644
index f8bc9c9fcb..0000000000
--- a/extensions/mentions/js/src/admin/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import app from 'flarum/admin/app';
-
-app.initializers.add('flarum-mentions', function () {
- app.extensionData
- .for('flarum-mentions')
- .registerSetting({
- setting: 'flarum-mentions.allow_username_format',
- type: 'boolean',
- label: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_label'),
- help: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_text'),
- })
- .registerPermission(
- {
- permission: 'mentionGroups',
- label: app.translator.trans('flarum-mentions.admin.permissions.mention_groups_label'),
- icon: 'fas fa-at',
- },
- 'start'
- );
-});
diff --git a/extensions/mentions/js/src/admin/index.tsx b/extensions/mentions/js/src/admin/index.tsx
new file mode 100644
index 0000000000..582eac3f04
--- /dev/null
+++ b/extensions/mentions/js/src/admin/index.tsx
@@ -0,0 +1,7 @@
+import app from 'flarum/admin/app';
+
+export { default as extend } from './extend';
+
+app.initializers.add('flarum-mentions', () => {
+ // ...
+});
diff --git a/extensions/mentions/js/src/common/extend.ts b/extensions/mentions/js/src/common/extend.ts
new file mode 100644
index 0000000000..593daeae7b
--- /dev/null
+++ b/extensions/mentions/js/src/common/extend.ts
@@ -0,0 +1,7 @@
+import Extend from 'flarum/common/extenders';
+import MentionedGambit from './query/posts/MentionedGambit';
+
+export default [
+ new Extend.Search() //
+ .gambit('posts', MentionedGambit),
+];
diff --git a/extensions/mentions/js/src/common/query/posts/MentionedGambit.ts b/extensions/mentions/js/src/common/query/posts/MentionedGambit.ts
new file mode 100644
index 0000000000..7ccd1b30a1
--- /dev/null
+++ b/extensions/mentions/js/src/common/query/posts/MentionedGambit.ts
@@ -0,0 +1,16 @@
+import { KeyValueGambit } from 'flarum/common/query/IGambit';
+import app from 'flarum/common/app';
+
+export default class MentionedGambit extends KeyValueGambit {
+ key(): string {
+ return app.translator.trans('flarum-mentions.lib.gambits.posts.mentioned.key', {}, true);
+ }
+
+ hint(): string {
+ return app.translator.trans('flarum-mentions.lib.gambits.posts.mentioned.hint', {}, true);
+ }
+
+ filterKey(): string {
+ return 'mentioned';
+ }
+}
diff --git a/extensions/mentions/js/src/forum/addComposerAutocomplete.js b/extensions/mentions/js/src/forum/addComposerAutocomplete.js
index 09808a56a5..ddcc7a2a0c 100644
--- a/extensions/mentions/js/src/forum/addComposerAutocomplete.js
+++ b/extensions/mentions/js/src/forum/addComposerAutocomplete.js
@@ -1,50 +1,34 @@
import app from 'flarum/forum/app';
import { extend } from 'flarum/common/extend';
-import TextEditor from 'flarum/common/components/TextEditor';
import TextEditorButton from 'flarum/common/components/TextEditorButton';
import KeyboardNavigatable from 'flarum/common/utils/KeyboardNavigatable';
+import AutocompleteReader from 'flarum/common/utils/AutocompleteReader';
+import { throttle } from 'flarum/common/utils/throttleDebounce';
import AutocompleteDropdown from './fragments/AutocompleteDropdown';
-import MentionFormats from './mentionables/formats/MentionFormats';
import MentionableModels from './mentionables/MentionableModels';
export default function addComposerAutocomplete() {
- app.mentionFormats = new MentionFormats();
-
- const $container = $('');
- const dropdown = new AutocompleteDropdown();
-
- extend(TextEditor.prototype, 'oncreate', function () {
+ extend('flarum/common/components/TextEditor', 'onbuild', function () {
+ this.mentionsDropdown = new AutocompleteDropdown();
+ this.searchMentions = throttle(250, (mentionables, buildSuggestions) => mentionables.search().then(buildSuggestions));
const $editor = this.$('.TextEditor-editor').wrap('');
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))
+ .when(() => this.mentionsDropdown.active)
+ .onUp(() => this.mentionsDropdown.navigate(-1))
+ .onDown(() => this.mentionsDropdown.navigate(1))
+ .onSelect(this.mentionsDropdown.complete.bind(this.mentionsDropdown))
+ .onCancel(this.mentionsDropdown.hide.bind(this.mentionsDropdown))
.bindTo($editor);
- $editor.after($container);
+ $editor.after($(''));
});
- extend(TextEditor.prototype, 'buildEditorParams', function (params) {
- let relMentionStart;
- let absMentionStart;
+ extend('flarum/common/components/TextEditor', 'buildEditorParams', function (params) {
let matchTyped;
- let mentionables = new MentionableModels({
- onmouseenter: function () {
- dropdown.setIndex($(this).parent().index());
- },
- onclick: (replacement) => {
- this.attrs.composer.editor.replaceBeforeCursor(absMentionStart - 1, replacement + ' ');
-
- dropdown.hide();
- },
- });
-
const suggestionsInputListener = () => {
const selection = this.attrs.composer.editor.getSelectionRange();
@@ -52,30 +36,27 @@ export default function addComposerAutocomplete() {
if (selection[1] - cursor > 0) return;
- // Search backwards from the cursor for a mention triggering symbol. If we find one,
- // we will want to show the correct autocomplete dropdown!
- // Check classes implementing the IMentionableModel interface to see triggering symbols.
- const lastChunk = this.attrs.composer.editor.getLastNChars(30);
- absMentionStart = 0;
let activeFormat = null;
- for (let i = lastChunk.length - 1; i >= 0; i--) {
- const character = lastChunk.substr(i, 1);
- activeFormat = app.mentionFormats.get(character);
-
- if (activeFormat && (i === 0 || /\s/.test(lastChunk.substr(i - 1, 1)))) {
- relMentionStart = i + 1;
- absMentionStart = cursor - lastChunk.length + i + 1;
- mentionables.init(activeFormat.makeMentionables());
- break;
- }
- }
-
- dropdown.hide();
- dropdown.active = false;
-
- if (absMentionStart) {
- const typed = lastChunk.substring(relMentionStart).toLowerCase();
- matchTyped = activeFormat.queryFromTyped(typed);
+ const autocompleteReader = new AutocompleteReader((character) => !!(activeFormat = app.mentionFormats.get(character)));
+ const autocompleting = autocompleteReader.check(this.attrs.composer.editor.getLastNChars(30), cursor, /\S+/);
+
+ const mentionsDropdown = this.mentionsDropdown;
+ let mentionables = new MentionableModels({
+ onmouseenter: function () {
+ mentionsDropdown.setIndex($(this).parent().index());
+ },
+ onclick: (replacement) => {
+ this.attrs.composer.editor.replaceBeforeCursor(autocompleting.absoluteStart - 1, replacement + ' ');
+ this.mentionsDropdown.hide();
+ },
+ });
+
+ this.mentionsDropdown.hide();
+ this.mentionsDropdown.active = false;
+
+ if (autocompleting) {
+ mentionables.init(activeFormat.makeMentionables());
+ matchTyped = activeFormat.queryFromTyped(autocompleting.typed);
if (!matchTyped) return;
@@ -87,14 +68,14 @@ export default function addComposerAutocomplete() {
const suggestions = mentionables.buildSuggestions();
if (suggestions.length) {
- dropdown.items = suggestions;
- m.render($container[0], dropdown.render());
-
- dropdown.show();
- const coordinates = this.attrs.composer.editor.getCaretCoordinates(absMentionStart);
- const width = dropdown.$().outerWidth();
- const height = dropdown.$().outerHeight();
- const parent = dropdown.$().offsetParent();
+ this.mentionsDropdown.items = suggestions;
+ m.render(this.$('.ComposerBody-mentionsDropdownContainer')[0], this.mentionsDropdown.render());
+
+ this.mentionsDropdown.show();
+ const coordinates = this.attrs.composer.editor.getCaretCoordinates(autocompleting.absoluteStart);
+ const width = this.mentionsDropdown.$().outerWidth();
+ const height = this.mentionsDropdown.$().outerHeight();
+ const parent = this.mentionsDropdown.$().offsetParent();
let left = coordinates.left;
let top = coordinates.top + 15;
@@ -110,28 +91,28 @@ export default function addComposerAutocomplete() {
top = Math.max(-(parent.offset().top - $(document).scrollTop()), top);
left = Math.max(-parent.offset().left, left);
- dropdown.show(left, top);
+ this.mentionsDropdown.show(left, top);
} else {
- dropdown.active = false;
- dropdown.hide();
+ this.mentionsDropdown.active = false;
+ this.mentionsDropdown.hide();
}
};
- dropdown.active = true;
+ this.mentionsDropdown.active = true;
buildSuggestions();
- dropdown.setIndex(0);
- dropdown.$().scrollTop(0);
+ this.mentionsDropdown.setIndex(0);
+ this.mentionsDropdown.$().scrollTop(0);
- mentionables.search()?.then(buildSuggestions);
+ this.searchMentions(mentionables, buildSuggestions);
}
};
params.inputListeners.push(suggestionsInputListener);
});
- extend(TextEditor.prototype, 'toolbarItems', function (items) {
+ extend('flarum/common/components/TextEditor', 'toolbarItems', function (items) {
items.add(
'mention',
this.attrs.composer.editor.insertAtCursor(' @')} icon="fas fa-at">
diff --git a/extensions/mentions/js/src/forum/addMentionedByList.js b/extensions/mentions/js/src/forum/addMentionedByList.js
index 45b54023dc..ff61ed061a 100644
--- a/extensions/mentions/js/src/forum/addMentionedByList.js
+++ b/extensions/mentions/js/src/forum/addMentionedByList.js
@@ -5,7 +5,7 @@ import Link from 'flarum/common/components/Link';
import PostPreview from 'flarum/forum/components/PostPreview';
import punctuateSeries from 'flarum/common/helpers/punctuateSeries';
import username from 'flarum/common/helpers/username';
-import icon from 'flarum/common/helpers/icon';
+import Icon from 'flarum/common/components/Icon';
import Button from 'flarum/common/components/Button';
import MentionedByModal from './components/MentionedByModal';
@@ -54,7 +54,9 @@ export default function addMentionedByList() {
}}
>
- {icon('fas fa-reply-all')}
+
+
+
{app.translator.trans('flarum-mentions.forum.post.mentioned_by_more_text', { count: post.mentionedByCount() - replies.length })}
@@ -118,7 +120,7 @@ export default function addMentionedByList() {
});
const limit = 4;
- const overLimit = repliers.length > limit;
+ const overLimit = post.mentionedByCount() > 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.
@@ -136,7 +138,7 @@ export default function addMentionedByList() {
// 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;
+ const count = post.mentionedByCount() - names.length;
names.push(app.translator.trans('flarum-mentions.forum.post.others_text', { count }));
}
@@ -145,7 +147,7 @@ export default function addMentionedByList() {
'replies',
- {icon('fas fa-reply')}
+
{app.translator.trans(`flarum-mentions.forum.post.mentioned_by${repliers[0].user() === app.session.user ? '_self' : ''}_text`, {
count: names.length,
users: punctuateSeries(names),
diff --git a/extensions/mentions/js/src/forum/addPostReplyAction.js b/extensions/mentions/js/src/forum/addPostReplyAction.js
index 395d96def1..ff8c16f143 100644
--- a/extensions/mentions/js/src/forum/addPostReplyAction.js
+++ b/extensions/mentions/js/src/forum/addPostReplyAction.js
@@ -5,7 +5,7 @@ import CommentPost from 'flarum/forum/components/CommentPost';
import reply from './utils/reply';
-export default function () {
+export default function addPostReplyAction() {
extend(CommentPost.prototype, 'actionItems', function (items) {
const post = this.attrs.post;
diff --git a/extensions/mentions/js/src/forum/compat.js b/extensions/mentions/js/src/forum/compat.js
deleted file mode 100644
index 8c456f1894..0000000000
--- a/extensions/mentions/js/src/forum/compat.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import GroupMentionedNotification from './components/GroupMentionedNotification';
-import MentionsUserPage from './components/MentionsUserPage';
-import PostMentionedNotification from './components/PostMentionedNotification';
-import UserMentionedNotification from './components/UserMentionedNotification';
-import AutocompleteDropdown from './fragments/AutocompleteDropdown';
-import PostQuoteButton from './fragments/PostQuoteButton';
-import getCleanDisplayName from './utils/getCleanDisplayName';
-import getMentionText from './utils/getMentionText';
-import * as reply from './utils/reply';
-import selectedText from './utils/selectedText';
-import * as textFormatter from './utils/textFormatter';
-import MentionableModel from './mentionables/MentionableModel';
-import MentionFormat from './mentionables/formats/MentionFormat';
-import Mentionables from './extenders/Mentionables';
-
-export default {
- 'mentions/components/MentionsUserPage': MentionsUserPage,
- 'mentions/components/PostMentionedNotification': PostMentionedNotification,
- 'mentions/components/UserMentionedNotification': UserMentionedNotification,
- 'mentions/components/GroupMentionedNotification': GroupMentionedNotification,
- 'mentions/fragments/AutocompleteDropdown': AutocompleteDropdown,
- 'mentions/fragments/PostQuoteButton': PostQuoteButton,
- 'mentions/utils/getCleanDisplayName': getCleanDisplayName,
- 'mentions/utils/getMentionText': getMentionText,
- 'mentions/utils/reply': reply,
- 'mentions/utils/selectedText': selectedText,
- 'mentions/utils/textFormatter': textFormatter,
- 'mentions/mentionables/MentionableModel': MentionableModel,
- 'mentions/mentionables/formats/MentionFormat': MentionFormat,
- 'mentions/extenders/Mentionables': Mentionables,
-};
diff --git a/extensions/mentions/js/src/forum/components/MentionedByModal.tsx b/extensions/mentions/js/src/forum/components/MentionedByModal.tsx
index 9a5d431a06..5638a13523 100644
--- a/extensions/mentions/js/src/forum/components/MentionedByModal.tsx
+++ b/extensions/mentions/js/src/forum/components/MentionedByModal.tsx
@@ -6,6 +6,7 @@ import type Post from 'flarum/common/models/Post';
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
import Button from 'flarum/common/components/Button';
import MentionedByModalState from '../state/MentionedByModalState';
+import Form from 'flarum/common/components/Form';
export interface IMentionedByModalAttrs extends IInternalModalAttrs {
post: Post;
@@ -58,13 +59,13 @@ export default class MentionedByModal
{this.state.hasNext() && (
-
+
+
)}
>
diff --git a/extensions/mentions/js/src/forum/components/MentionsUserPage.js b/extensions/mentions/js/src/forum/components/MentionsUserPage.js
deleted file mode 100644
index 4ac4cec4c2..0000000000
--- a/extensions/mentions/js/src/forum/components/MentionsUserPage.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import app from 'flarum/forum/app';
-import PostsUserPage from 'flarum/forum/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: '-createdAt',
- });
- }
-}
diff --git a/extensions/mentions/js/src/forum/components/MentionsUserPage.ts b/extensions/mentions/js/src/forum/components/MentionsUserPage.ts
new file mode 100644
index 0000000000..c92e9d4943
--- /dev/null
+++ b/extensions/mentions/js/src/forum/components/MentionsUserPage.ts
@@ -0,0 +1,16 @@
+import PostsUserPage from 'flarum/forum/components/PostsUserPage';
+import type User from 'flarum/common/models/User';
+
+/**
+ * The `MentionsUserPage` component shows post which user Mentioned at
+ */
+export default class MentionsUserPage extends PostsUserPage {
+ params(user: User) {
+ return {
+ filter: {
+ type: 'comment',
+ mentioned: user.id(),
+ },
+ };
+ }
+}
diff --git a/extensions/mentions/js/src/forum/extend.ts b/extensions/mentions/js/src/forum/extend.ts
index e14c8fca31..2a31ed0fee 100644
--- a/extensions/mentions/js/src/forum/extend.ts
+++ b/extensions/mentions/js/src/forum/extend.ts
@@ -2,8 +2,15 @@ import Extend from 'flarum/common/extenders';
import Post from 'flarum/common/models/Post';
import User from 'flarum/common/models/User';
import MentionsUserPage from './components/MentionsUserPage';
+import PostMentionedNotification from './components/PostMentionedNotification';
+import UserMentionedNotification from './components/UserMentionedNotification';
+import GroupMentionedNotification from './components/GroupMentionedNotification';
+
+import commonExtend from '../common/extend';
export default [
+ ...commonExtend,
+
new Extend.Routes() //
.add('user.mentions', '/u/:username/mentions', MentionsUserPage),
@@ -11,6 +18,11 @@ export default [
.hasMany('mentionedBy')
.attribute('mentionedByCount'),
+ new Extend.Notification() //
+ .add('postMentioned', PostMentionedNotification)
+ .add('userMentioned', UserMentionedNotification)
+ .add('groupMentioned', GroupMentionedNotification),
+
new Extend.Model(User) //
.attribute('canMentionGroups'),
];
diff --git a/extensions/mentions/js/src/forum/extenders/Mentionables.ts b/extensions/mentions/js/src/forum/extenders/Mentionables.ts
index cf2db51f7d..46423ae543 100644
--- a/extensions/mentions/js/src/forum/extenders/Mentionables.ts
+++ b/extensions/mentions/js/src/forum/extenders/Mentionables.ts
@@ -5,7 +5,7 @@ import type MentionFormat from '../mentionables/formats/MentionFormat';
export default class Mentionables implements IExtender {
protected formats: (new () => MentionFormat)[] = [];
- protected mentionables: Record MentionableModel)[]> = {};
+ protected mentionables: Record MentionableModel)[]> = {};
/**
* Register a new mention format.
@@ -26,7 +26,7 @@ export default class Mentionables implements IExtender {
* @param mentionable The mentionable instance to register.
* Must extend MentionableModel.
*/
- mentionable(symbol: string, mentionable: new () => MentionableModel): this {
+ mentionable(symbol: string, mentionable: new (...args: any[]) => MentionableModel): this {
if (!this.mentionables[symbol]) {
this.mentionables[symbol] = [];
}
diff --git a/extensions/mentions/js/src/forum/forum.js b/extensions/mentions/js/src/forum/forum.js
new file mode 100644
index 0000000000..a298d08a32
--- /dev/null
+++ b/extensions/mentions/js/src/forum/forum.js
@@ -0,0 +1,13 @@
+import './components/GroupMentionedNotification';
+import './components/MentionsUserPage';
+import './components/PostMentionedNotification';
+import './components/UserMentionedNotification';
+import './fragments/AutocompleteDropdown';
+import './fragments/PostQuoteButton';
+import './utils/getCleanDisplayName';
+import './utils/reply';
+import './utils/selectedText';
+import './utils/textFormatter';
+import './mentionables/MentionableModel';
+import './mentionables/formats/MentionFormat';
+import './extenders/Mentionables';
diff --git a/extensions/mentions/js/src/forum/fragments/PostQuoteButton.js b/extensions/mentions/js/src/forum/fragments/PostQuoteButton.js
index 79aca7f9f3..378b15ddb4 100644
--- a/extensions/mentions/js/src/forum/fragments/PostQuoteButton.js
+++ b/extensions/mentions/js/src/forum/fragments/PostQuoteButton.js
@@ -1,6 +1,6 @@
import app from 'flarum/forum/app';
import Fragment from 'flarum/common/Fragment';
-import icon from 'flarum/common/helpers/icon';
+import Icon from 'flarum/common/components/Icon';
import reply from '../utils/reply';
@@ -19,7 +19,7 @@ export default class PostQuoteButton extends Fragment {
reply(this.post, this.content);
}}
>
- {icon('fas fa-quote-left', { className: 'Button-icon' })}
+
{app.translator.trans('flarum-mentions.forum.post.quote_button')}
);
diff --git a/extensions/mentions/js/src/forum/index.js b/extensions/mentions/js/src/forum/index.js
index 7323a00d70..fb5f5fcb17 100644
--- a/extensions/mentions/js/src/forum/index.js
+++ b/extensions/mentions/js/src/forum/index.js
@@ -1,6 +1,5 @@
-import { extend } from 'flarum/common/extend';
+import { extend, override } from 'flarum/common/extend';
import app from 'flarum/forum/app';
-import NotificationGrid from 'flarum/forum/components/NotificationGrid';
import { getPlainContent } from 'flarum/common/utils/string';
import textContrastClass from 'flarum/common/helpers/textContrastClass';
import Post from 'flarum/forum/components/Post';
@@ -10,17 +9,15 @@ 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 GroupMentionedNotification from './components/GroupMentionedNotification';
+import MentionFormats from './mentionables/formats/MentionFormats';
import UserPage from 'flarum/forum/components/UserPage';
import LinkButton from 'flarum/common/components/LinkButton';
-import User from 'flarum/common/models/User';
-import Model from 'flarum/common/Model';
+
+app.mentionFormats = new MentionFormats();
export { default as extend } from './extend';
-app.initializers.add('flarum-mentions', function () {
+app.initializers.add('flarum-mentions', () => {
// 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();
@@ -40,12 +37,8 @@ app.initializers.add('flarum-mentions', function () {
// posts or users that the user could mention.
addComposerAutocomplete();
- app.notificationComponents.postMentioned = PostMentionedNotification;
- app.notificationComponents.userMentioned = UserMentionedNotification;
- app.notificationComponents.groupMentioned = GroupMentionedNotification;
-
// Add notification preferences.
- extend(NotificationGrid.prototype, 'notificationTypes', function (items) {
+ extend('flarum/forum/components/NotificationGrid', 'notificationTypes', function (items) {
items.add('postMentioned', {
name: 'postMentioned',
icon: 'fas fa-reply',
@@ -86,12 +79,24 @@ app.initializers.add('flarum-mentions', function () {
this.classList.add(textContrastClass(getComputedStyle(this).getPropertyValue('--color')));
});
});
+
+ // Auto scope the search to the current user mentioned posts.
+ override('flarum/forum/components/SearchModal', 'defaultActiveSource', function (original) {
+ const orig = original();
+
+ if (!orig && app.current.data.routeName && app.current.data.routeName.includes('user.mentions') && app.current.data.user) {
+ return 'posts';
+ }
+
+ return orig;
+ });
+ extend('flarum/forum/components/SearchModal', 'defaultFilters', function (filters) {
+ if (app.current.data.routeName && app.current.data.routeName.includes('user.mentions') && app.current.data.user) {
+ filters.posts.mentioned = app.current.data.user.username();
+ }
+ });
});
export * from './utils/textFormatter';
-// Expose compat API
-import mentionsCompat from './compat';
-import { compat } from '@flarum/core/forum';
-
-Object.assign(compat, mentionsCompat);
+import './forum';
diff --git a/extensions/mentions/js/src/forum/mentionables/MentionableModels.tsx b/extensions/mentions/js/src/forum/mentionables/MentionableModels.tsx
index 4a4435ef23..a738476dec 100644
--- a/extensions/mentions/js/src/forum/mentionables/MentionableModels.tsx
+++ b/extensions/mentions/js/src/forum/mentionables/MentionableModels.tsx
@@ -2,7 +2,6 @@ import type MentionableModel from './MentionableModel';
import type Model from 'flarum/common/Model';
import type Mithril from 'mithril';
import MentionsDropdownItem from '../components/MentionsDropdownItem';
-import { throttle } from 'flarum/common/utils/throttleDebounce';
export default class MentionableModels {
protected mentionables?: MentionableModel[];
@@ -33,7 +32,7 @@ export default class MentionableModels {
* Don't send API calls searching for models until at least 2 characters have been typed.
* This focuses the mention results on models already loaded.
*/
- public readonly search = throttle(250, async (): Promise => {
+ public readonly search = async (): Promise => {
if (!this.typed || this.typed.length <= 1) return;
const typedLower = this.typed.toLowerCase();
@@ -51,7 +50,7 @@ export default class MentionableModels {
this.searched.push(typedLower);
return Promise.resolve();
- });
+ };
public matches(mentionable: MentionableModel, model: Model): boolean {
return mentionable.matches(model, this.typed?.toLowerCase() || '');
diff --git a/extensions/mentions/js/src/forum/mentionables/PostMention.tsx b/extensions/mentions/js/src/forum/mentionables/PostMention.tsx
index 172fa8f46b..d645417808 100644
--- a/extensions/mentions/js/src/forum/mentionables/PostMention.tsx
+++ b/extensions/mentions/js/src/forum/mentionables/PostMention.tsx
@@ -3,11 +3,9 @@ import MentionableModel from './MentionableModel';
import type Post from 'flarum/common/models/Post';
import type Mithril from 'mithril';
import usernameHelper from 'flarum/common/helpers/username';
-import avatar from 'flarum/common/helpers/avatar';
+import Avatar from 'flarum/common/components/Avatar';
import highlight from 'flarum/common/helpers/highlight';
import { truncate } from 'flarum/common/utils/string';
-import ReplyComposer from 'flarum/forum/components/ReplyComposer';
-import EditPostComposer from 'flarum/forum/components/EditPostComposer';
import getCleanDisplayName from '../utils/getCleanDisplayName';
import type AtMentionFormat from './formats/AtMentionFormat';
@@ -23,7 +21,10 @@ export default class PostMention extends MentionableModel
* match any username characters that have been typed.
*/
initialResults(): Post[] {
- if (!app.composer.bodyMatches(ReplyComposer) && !app.composer.bodyMatches(EditPostComposer)) {
+ const EditPostComposer = flarum.reg.checkModule('core', 'forum/components/EditPostComposer');
+ const ReplyComposer = flarum.reg.checkModule('core', 'forum/components/ReplyComposer');
+
+ if ((!ReplyComposer || !app.composer.bodyMatches(ReplyComposer)) && (!EditPostComposer || !app.composer.bodyMatches(EditPostComposer))) {
return [];
}
@@ -62,7 +63,7 @@ export default class PostMention extends MentionableModel
return (
<>
- {avatar(user)}
+
{username}
{[
app.translator.trans('flarum-mentions.forum.composer.reply_to_post_text', { number: model.number() }),
diff --git a/extensions/mentions/js/src/forum/mentionables/TagMention.tsx b/extensions/mentions/js/src/forum/mentionables/TagMention.tsx
index eaa480d9a7..78ff31b8f4 100644
--- a/extensions/mentions/js/src/forum/mentionables/TagMention.tsx
+++ b/extensions/mentions/js/src/forum/mentionables/TagMention.tsx
@@ -1,7 +1,7 @@
import app from 'flarum/forum/app';
import Badge from 'flarum/common/components/Badge';
import highlight from 'flarum/common/helpers/highlight';
-import type Tag from 'flarum/tags/common/models/Tag';
+import type Tag from 'ext:flarum/tags/common/models/Tag';
import type Mithril from 'mithril';
import MentionableModel from './MentionableModel';
import type HashMentionFormat from './formats/HashMentionFormat';
diff --git a/extensions/mentions/js/src/forum/mentionables/UserMention.tsx b/extensions/mentions/js/src/forum/mentionables/UserMention.tsx
index 78fb688242..c3b80a9e34 100644
--- a/extensions/mentions/js/src/forum/mentionables/UserMention.tsx
+++ b/extensions/mentions/js/src/forum/mentionables/UserMention.tsx
@@ -2,7 +2,7 @@ import app from 'flarum/forum/app';
import type Mithril from 'mithril';
import type User from 'flarum/common/models/User';
import usernameHelper from 'flarum/common/helpers/username';
-import avatar from 'flarum/common/helpers/avatar';
+import Avatar from 'flarum/common/components/Avatar';
import highlight from 'flarum/common/helpers/highlight';
import MentionableModel from './MentionableModel';
import getCleanDisplayName, { shouldUseOldFormat } from '../utils/getCleanDisplayName';
@@ -46,7 +46,7 @@ export default class UserMention extends MentionableModel
return (
<>
- {avatar(model)}
+
{username}
>
);
diff --git a/extensions/mentions/js/src/forum/mentionables/formats/HashMentionFormat.ts b/extensions/mentions/js/src/forum/mentionables/formats/HashMentionFormat.ts
index a01bf00e9b..ef30d50c91 100644
--- a/extensions/mentions/js/src/forum/mentionables/formats/HashMentionFormat.ts
+++ b/extensions/mentions/js/src/forum/mentionables/formats/HashMentionFormat.ts
@@ -4,7 +4,7 @@ import TagMention from '../TagMention';
export default class HashMentionFormat extends MentionFormat {
public mentionables: (new (...args: any[]) => MentionableModel)[] = [TagMention];
- protected extendable: boolean = false;
+ protected extendable: boolean = true;
public trigger(): string {
return '#';
diff --git a/extensions/mentions/js/src/forum/utils/getMentionText.js b/extensions/mentions/js/src/forum/utils/getMentionText.js
deleted file mode 100644
index 446ffca844..0000000000
--- a/extensions/mentions/js/src/forum/utils/getMentionText.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import app from 'flarum/forum/app';
-
-/**
- * Fetches the mention text for a specified user (and optionally a post ID for replies or group).
- *
- * Automatically determines which mention syntax to be used based on the option in the
- * admin dashboard. Also performs display name clean-up automatically.
- *
- * @deprecated Use `app.mentionables.get('user').replacement(user)` instead. Will be removed in 2.0.
- */
-export default function getMentionText(user, postId, group) {
- if (user !== undefined && postId === undefined) {
- return app.mentionables.get('user').replacement(user);
- } else if (user !== undefined && postId !== undefined) {
- return app.mentionables.get('post').replacement(app.store.getById('posts', postId));
- } else if (group !== undefined) {
- return app.mentionables.get('group').replacement(group);
- }
-
- throw 'No parameters were passed';
-}
diff --git a/extensions/mentions/js/src/forum/utils/reply.js b/extensions/mentions/js/src/forum/utils/reply.js
index 5a2961e77b..816ed83ef6 100644
--- a/extensions/mentions/js/src/forum/utils/reply.js
+++ b/extensions/mentions/js/src/forum/utils/reply.js
@@ -1,6 +1,5 @@
import app from 'flarum/forum/app';
import DiscussionControls from 'flarum/forum/utils/DiscussionControls';
-import EditPostComposer from 'flarum/forum/components/EditPostComposer';
export function insertMention(post, composer, quote) {
return new Promise((resolve) => {
@@ -27,7 +26,9 @@ export function insertMention(post, composer, quote) {
}
export default function reply(post, quote) {
- if (app.composer.bodyMatches(EditPostComposer) && app.composer.body.attrs.post.discussion() === post.discussion()) {
+ const EditPostComposer = flarum.reg.checkModule('core', 'forum/components/EditPostComposer');
+
+ if (EditPostComposer && app.composer.bodyMatches(EditPostComposer) && app.composer.body.attrs.post.discussion() === post.discussion()) {
// If we're already editing a post in the discussion of post we're quoting,
// insert the mention directly.
return insertMention(post, app.composer, quote);
diff --git a/extensions/mentions/js/tsconfig.json b/extensions/mentions/js/tsconfig.json
index f05741cea4..8e6f36ec0b 100644
--- a/extensions/mentions/js/tsconfig.json
+++ b/extensions/mentions/js/tsconfig.json
@@ -10,12 +10,7 @@
"declarationDir": "./dist-typings",
"paths": {
"flarum/*": ["../../../framework/core/js/dist-typings/*"],
- "flarum/tags/*": ["../../tags/js/dist-typings/*"],
- // TODO: remove after export registry system implemented
- // Without this, the old-style `@flarum/core` import is resolved to
- // source code in flarum/core instead of the dist typings.
- // This causes an inaccurate "duplicate export" error.
- "@flarum/core/*": ["../../../framework/core/js/dist-typings/*"],
+ "ext:flarum/tags/*": ["../../tags/js/dist-typings/*"]
}
}
}
diff --git a/extensions/mentions/less/forum.less b/extensions/mentions/less/forum.less
index d5d9e819fc..cb7e3a8138 100644
--- a/extensions/mentions/less/forum.less
+++ b/extensions/mentions/less/forum.less
@@ -1,7 +1,7 @@
.PostMention, .UserMention, .GroupMention {
background: var(--control-bg);
color: var(--control-color);
- border-radius: @border-radius;
+ border-radius: var(--border-radius);
font-weight: 600;
blockquote & {
@@ -30,10 +30,7 @@
margin-left: 0;
}
- // @TODO: 2.0 use an icon in the XSLT template.
- &:before {
- .fas();
- content: @fa-var-reply;
+ .icon {
margin-right: 5px;
}
}
@@ -81,7 +78,7 @@
.MentionsDropdown, .PostMention-preview, .Post-mentionedBy-preview {
// @TODO: Rename to .MentionsDropdownItem, along with child classes. 2.0
.PostPreview {
- color: @muted-color;
+ color: var(--muted-color);
.Avatar {
.Avatar--size(24px);
@@ -93,7 +90,7 @@
}
}
.username {
- color: @text-color;
+ color: var(--text-color);
font-weight: bold;
}
}
diff --git a/extensions/mentions/locale/en.yml b/extensions/mentions/locale/en.yml
index 108af93797..7457c54ee6 100644
--- a/extensions/mentions/locale/en.yml
+++ b/extensions/mentions/locale/en.yml
@@ -70,40 +70,53 @@ flarum-mentions:
# These translations are used in emails sent when a post is replied to
post_mentioned:
subject: "{replier_display_name} replied to your post in {title}"
- body: |
- Hey {recipient_display_name}!
+ plain:
+ body: |
+ {replier_display_name} replied to your post (#{post_number}) in {title}.
- {replier_display_name} replied to your post (#{post_number}) in {title}.
+ {url}
- {url}
+ ---
- ---
-
- {content}
+ {content}
+ html:
+ body: "{replier_display_name} replied to your post (#{post_number}) in [{title}]({url})."
# These translations are used in emails sent when a user is mentioned
user_mentioned:
subject: "{mentioner_display_name} mentioned you in {title}"
- body: |
- Hey {recipient_display_name}!
-
- {mentioner_display_name} mentioned you in a post in {title}.
+ plain:
+ body: |
+ {mentioner_display_name} mentioned you in a post in {title}.
- {url}
+ {url}
- ---
+ ---
- {content}
+ {content}
+ html:
+ body: "{mentioner_display_name} mentioned you in a post in [{title}]({url})."
# These translations are used in emails sent when a group is mentioned
group_mentioned:
subject: "{mentioner_display_name} mentioned a group you're a member of in {title}"
- body: |
- Hey {recipient_display_name}!
+ plain:
+ body: |
+ {mentioner_display_name} mentioned a group you're a member of in {title}.
+
+ {url}
- {mentioner_display_name} mentioned a group you're a member of in {title}.
+ ---
- {url}
+ {content}
+ html:
+ body: "{mentioner_display_name} mentioned a group you're a member of in [{title}]({url})."
- ---
+ # Translations in this namespace are used by the forum and admin interfaces.
+ lib:
- {content}
+ # These translations are used by gambits. Gambit keys must be in snake_case, no spaces.
+ gambits:
+ posts:
+ mentioned:
+ key: mentioned
+ hint: The ID or username of the mentioned user
diff --git a/extensions/mentions/migrations/2022_05_20_000005_add_created_at_to_post_mentions_post_table.php b/extensions/mentions/migrations/2022_05_20_000005_add_created_at_to_post_mentions_post_table.php
index d6c4114e1d..56c60aa2c3 100644
--- a/extensions/mentions/migrations/2022_05_20_000005_add_created_at_to_post_mentions_post_table.php
+++ b/extensions/mentions/migrations/2022_05_20_000005_add_created_at_to_post_mentions_post_table.php
@@ -16,10 +16,9 @@
$table->timestamp('created_at')->nullable();
});
- // do this manually because dbal doesn't recognize timestamp columns
- $connection = $schema->getConnection();
- $prefix = $connection->getTablePrefix();
- $connection->statement("ALTER TABLE `{$prefix}post_mentions_post` MODIFY created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP");
+ $schema->table('post_mentions_post', function (Blueprint $table) {
+ $table->timestamp('created_at')->nullable()->useCurrent()->change();
+ });
},
'down' => function (Builder $schema) {
diff --git a/extensions/mentions/migrations/2022_05_20_000006_add_created_at_to_post_mentions_user_table.php b/extensions/mentions/migrations/2022_05_20_000006_add_created_at_to_post_mentions_user_table.php
index f2991d6074..7361a64364 100644
--- a/extensions/mentions/migrations/2022_05_20_000006_add_created_at_to_post_mentions_user_table.php
+++ b/extensions/mentions/migrations/2022_05_20_000006_add_created_at_to_post_mentions_user_table.php
@@ -16,10 +16,9 @@
$table->timestamp('created_at')->nullable();
});
- // do this manually because dbal doesn't recognize timestamp columns
- $connection = $schema->getConnection();
- $prefix = $connection->getTablePrefix();
- $connection->statement("ALTER TABLE `{$prefix}post_mentions_user` MODIFY created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP");
+ $schema->table('post_mentions_user', function (Blueprint $table) {
+ $table->timestamp('created_at')->nullable()->useCurrent()->change();
+ });
},
'down' => function (Builder $schema) {
diff --git a/extensions/mentions/src/Api/LoadMentionedByRelationship.php b/extensions/mentions/src/Api/LoadMentionedByRelationship.php
deleted file mode 100644
index 82f916869a..0000000000
--- a/extensions/mentions/src/Api/LoadMentionedByRelationship.php
+++ /dev/null
@@ -1,72 +0,0 @@
-with(['mentionsPosts', 'mentionsPosts.user', 'mentionsUsers'])
- ->whereVisibleTo($actor)
- ->oldest()
- // Limiting a relationship results is only possible because
- // the Post model uses the \Staudenmeir\EloquentEagerLimit\HasEagerLimit
- // trait.
- ->limit(self::$maxMentionedBy);
- }
-
- /**
- * Called using the @see ApiController::prepareDataForSerialization extender.
- */
- public static function countRelation(AbstractSerializeController $controller, mixed $data, ServerRequestInterface $request): array
- {
- $actor = RequestUtil::getActor($request);
- $loadable = null;
-
- if ($data instanceof Discussion) {
- // We do this because the ShowDiscussionController manipulates the posts
- // in a way that some of them are just ids.
- $loadable = $data->posts->filter(function ($post) {
- return $post instanceof Post;
- });
- } elseif ($data instanceof Collection) {
- $loadable = $data;
- } elseif ($data instanceof Post) {
- $loadable = $data->newCollection([$data]);
- }
-
- if ($loadable) {
- $loadable->loadCount([
- 'mentionedBy' => function ($query) use ($actor) {
- return $query->whereVisibleTo($actor);
- }
- ]);
- }
-
- return [];
- }
-}
diff --git a/extensions/mentions/src/Api/PostResourceFields.php b/extensions/mentions/src/Api/PostResourceFields.php
new file mode 100644
index 0000000000..3793acca61
--- /dev/null
+++ b/extensions/mentions/src/Api/PostResourceFields.php
@@ -0,0 +1,41 @@
+countRelation('mentionedBy', function (Builder $query, Context $context) {
+ $query->whereVisibleTo($context->getActor());
+ }),
+
+ Schema\Relationship\ToMany::make('mentionedBy')
+ ->type('posts')
+ ->includable()
+ ->scope(fn (BelongsToMany $query) => $query->oldest('id')->limit(static::$maxMentionedBy)),
+ Schema\Relationship\ToMany::make('mentionsPosts')
+ ->type('posts'),
+ Schema\Relationship\ToMany::make('mentionsUsers')
+ ->type('users'),
+ Schema\Relationship\ToMany::make('mentionsGroups')
+ ->type('groups'),
+ ];
+ }
+}
diff --git a/extensions/mentions/src/ConfigureMentions.php b/extensions/mentions/src/ConfigureMentions.php
index 2670dbb2e1..6c0107e800 100644
--- a/extensions/mentions/src/ConfigureMentions.php
+++ b/extensions/mentions/src/ConfigureMentions.php
@@ -122,10 +122,10 @@ private function configurePostMentions(Configurator $config): void
$tag->template = '
-
+
-
+ ';
diff --git a/extensions/mentions/src/Filter/MentionedFilter.php b/extensions/mentions/src/Filter/MentionedFilter.php
index a0b470d932..4c5e11a9ec 100644
--- a/extensions/mentions/src/Filter/MentionedFilter.php
+++ b/extensions/mentions/src/Filter/MentionedFilter.php
@@ -9,24 +9,40 @@
namespace Flarum\Mentions\Filter;
-use Flarum\Filter\FilterInterface;
-use Flarum\Filter\FilterState;
-use Flarum\Filter\ValidateFilterTrait;
+use Flarum\Search\Database\DatabaseSearchState;
+use Flarum\Search\Filter\FilterInterface;
+use Flarum\Search\SearchState;
+use Flarum\Search\ValidateFilterTrait;
+use Flarum\User\UserRepository;
+/**
+ * @implements FilterInterface
+ */
class MentionedFilter implements FilterInterface
{
use ValidateFilterTrait;
+ public function __construct(
+ protected UserRepository $users
+ ) {
+ }
+
public function getFilterKey(): string
{
return 'mentioned';
}
- public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
+ public function filter(SearchState $state, string|array $value, bool $negate): void
{
- $mentionedId = $this->asInt($filterValue);
+ $mentionedUsername = $this->asString($value);
+
+ $mentionedId = $this->users->getIdForUsername($mentionedUsername);
+
+ if (! $mentionedId) {
+ $mentionedId = intval($mentionedUsername);
+ }
- $filterState
+ $state
->getQuery()
->join('post_mentions_user', 'posts.id', '=', 'post_mentions_user.post_id')
->where('post_mentions_user.mentions_user_id', $negate ? '!=' : '=', $mentionedId);
diff --git a/extensions/mentions/src/Filter/MentionedPostFilter.php b/extensions/mentions/src/Filter/MentionedPostFilter.php
index da46bbe2c9..193ba33953 100644
--- a/extensions/mentions/src/Filter/MentionedPostFilter.php
+++ b/extensions/mentions/src/Filter/MentionedPostFilter.php
@@ -9,9 +9,13 @@
namespace Flarum\Mentions\Filter;
-use Flarum\Filter\FilterInterface;
-use Flarum\Filter\FilterState;
+use Flarum\Search\Database\DatabaseSearchState;
+use Flarum\Search\Filter\FilterInterface;
+use Flarum\Search\SearchState;
+/**
+ * @implements FilterInterface
+ */
class MentionedPostFilter implements FilterInterface
{
public function getFilterKey(): string
@@ -19,11 +23,11 @@ public function getFilterKey(): string
return 'mentionedPost';
}
- public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
+ public function filter(SearchState $state, string|array $value, bool $negate): void
{
- $mentionedId = trim($filterValue, '"');
+ $mentionedId = trim($value, '"');
- $filterState
+ $state
->getQuery()
->join('post_mentions_post', 'posts.id', '=', 'post_mentions_post.post_id')
->where('post_mentions_post.mentions_post_id', $negate ? '!=' : '=', $mentionedId);
diff --git a/extensions/mentions/src/Formatter/FormatGroupMentions.php b/extensions/mentions/src/Formatter/FormatGroupMentions.php
index 0c8881a837..e9398f58e1 100644
--- a/extensions/mentions/src/Formatter/FormatGroupMentions.php
+++ b/extensions/mentions/src/Formatter/FormatGroupMentions.php
@@ -9,9 +9,9 @@
namespace Flarum\Mentions\Formatter;
+use Flarum\Database\AbstractModel;
use Flarum\Group\Group;
use Flarum\Locale\TranslatorInterface;
-use Flarum\Post\Post;
use s9e\TextFormatter\Renderer;
use s9e\TextFormatter\Utils;
@@ -25,8 +25,8 @@ public function __construct(
public function __invoke(Renderer $renderer, mixed $context, string $xml): string
{
return Utils::replaceAttributes($xml, 'GROUPMENTION', function ($attributes) use ($context) {
- $group = (($context && isset($context->getRelations()['mentionsGroups'])) || $context instanceof Post)
- ? $context->mentionsGroups->find($attributes['id'])
+ $group = ($context instanceof AbstractModel && $context->isRelation('mentionsGroups'))
+ ? $context->mentionsGroups->find($attributes['id']) // @phpstan-ignore-line
: Group::find($attributes['id']);
if ($group) {
diff --git a/extensions/mentions/src/Formatter/FormatPostMentions.php b/extensions/mentions/src/Formatter/FormatPostMentions.php
index ed17bcb791..f020f7f5ff 100644
--- a/extensions/mentions/src/Formatter/FormatPostMentions.php
+++ b/extensions/mentions/src/Formatter/FormatPostMentions.php
@@ -9,24 +9,32 @@
namespace Flarum\Mentions\Formatter;
+use Flarum\Database\AbstractModel;
+use Flarum\Discussion\Discussion;
+use Flarum\Http\SlugManager;
use Flarum\Locale\TranslatorInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
+use Flarum\Post\Post;
use s9e\TextFormatter\Renderer;
use s9e\TextFormatter\Utils;
class FormatPostMentions
{
public function __construct(
- private readonly TranslatorInterface $translator
+ private readonly TranslatorInterface $translator,
+ private readonly SlugManager $slugManager
) {
}
- public function __invoke(Renderer $renderer, mixed $context, ?string $xml, Request $request = null): string
+ /**
+ * Configure rendering for post mentions.
+ */
+ public function __invoke(Renderer $renderer, mixed $context, string $xml): string
{
- $post = $context;
+ return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($context) {
+ $post = ($context instanceof AbstractModel && $context->isRelation('mentionsPosts'))
+ ? $context->mentionsPosts->find($attributes['id']) // @phpstan-ignore-line
+ : Post::find($attributes['id']);
- return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($post) {
- $post = $post->mentionsPosts->find($attributes['id']);
if ($post && $post->user) {
$attributes['displayname'] = $post->user->display_name;
}
@@ -42,6 +50,12 @@ public function __invoke(Renderer $renderer, mixed $context, ?string $xml, Reque
$attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
}
+ if ($post) {
+ $attributes['discussionid'] = $this->slugManager
+ ->forResource(Discussion::class)
+ ->toSlug($post->discussion);
+ }
+
return $attributes;
});
}
diff --git a/extensions/mentions/src/Formatter/FormatTagMentions.php b/extensions/mentions/src/Formatter/FormatTagMentions.php
index b867969ca9..e3c15af69f 100644
--- a/extensions/mentions/src/Formatter/FormatTagMentions.php
+++ b/extensions/mentions/src/Formatter/FormatTagMentions.php
@@ -9,7 +9,7 @@
namespace Flarum\Mentions\Formatter;
-use Flarum\Post\Post;
+use Flarum\Database\AbstractModel;
use Flarum\Tags\Tag;
use Psr\Http\Message\ServerRequestInterface as Request;
use s9e\TextFormatter\Renderer;
@@ -17,12 +17,12 @@
class FormatTagMentions
{
- public function __invoke(Renderer $renderer, mixed $context, ?string $xml, Request $request = null): string
+ public function __invoke(Renderer $renderer, mixed $context, string $xml, Request $request = null): string
{
return Utils::replaceAttributes($xml, 'TAGMENTION', function ($attributes) use ($context) {
/** @var Tag|null $tag */
- $tag = (($context && isset($context->getRelations()['mentionsTags'])) || $context instanceof Post)
- ? $context->mentionsTags->find($attributes['id'])
+ $tag = ($context instanceof AbstractModel && $context->isRelation('mentionsTags'))
+ ? $context->mentionsTags->find($attributes['id']) // @phpstan-ignore-line
: Tag::query()->find($attributes['id']);
if ($tag) {
diff --git a/extensions/mentions/src/Formatter/FormatUserMentions.php b/extensions/mentions/src/Formatter/FormatUserMentions.php
index feefd58b44..74d002940f 100644
--- a/extensions/mentions/src/Formatter/FormatUserMentions.php
+++ b/extensions/mentions/src/Formatter/FormatUserMentions.php
@@ -9,9 +9,9 @@
namespace Flarum\Mentions\Formatter;
+use Flarum\Database\AbstractModel;
use Flarum\Http\SlugManager;
use Flarum\Locale\TranslatorInterface;
-use Flarum\Post\Post;
use Flarum\User\User;
use s9e\TextFormatter\Renderer;
use s9e\TextFormatter\Utils;
@@ -27,8 +27,8 @@ public function __construct(
public function __invoke(Renderer $renderer, mixed $context, string $xml): string
{
return Utils::replaceAttributes($xml, 'USERMENTION', function ($attributes) use ($context) {
- $user = (($context && isset($context->getRelations()['mentionsUsers'])) || $context instanceof Post)
- ? $context->mentionsUsers->find($attributes['id'])
+ $user = ($context instanceof AbstractModel && $context->isRelation('mentionsUsers'))
+ ? $context->mentionsUsers->find($attributes['id']) // @phpstan-ignore-line
: User::find($attributes['id']);
$attributes['deleted'] = false;
diff --git a/extensions/mentions/src/Formatter/UnparsePostMentions.php b/extensions/mentions/src/Formatter/UnparsePostMentions.php
index 519c3f4a16..c0e138846d 100644
--- a/extensions/mentions/src/Formatter/UnparsePostMentions.php
+++ b/extensions/mentions/src/Formatter/UnparsePostMentions.php
@@ -9,7 +9,9 @@
namespace Flarum\Mentions\Formatter;
+use Flarum\Database\AbstractModel;
use Flarum\Locale\TranslatorInterface;
+use Flarum\Post\Post;
use s9e\TextFormatter\Utils;
class UnparsePostMentions
@@ -31,10 +33,11 @@ public function __invoke(mixed $context, string $xml): string
*/
protected function updatePostMentionTags(mixed $context, string $xml): string
{
- $post = $context;
+ return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($context) {
+ $post = ($context instanceof AbstractModel && $context->isRelation('mentionsPosts'))
+ ? $context->mentionsPosts->find($attributes['id']) // @phpstan-ignore-line
+ : Post::find($attributes['id']);
- return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($post) {
- $post = $post->mentionsPosts->find($attributes['id']);
if ($post && $post->user) {
$attributes['displayname'] = $post->user->display_name;
}
@@ -47,7 +50,7 @@ protected function updatePostMentionTags(mixed $context, string $xml): string
$attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
}
- if (strpos($attributes['displayname'], '"#') !== false) {
+ if (str_contains($attributes['displayname'], '"#')) {
$attributes['displayname'] = preg_replace('/"#[a-z]{0,3}[0-9]+/', '_', $attributes['displayname']);
}
@@ -62,7 +65,7 @@ protected function unparsePostMentionTags(string $xml): string
{
$tagName = 'POSTMENTION';
- if (strpos($xml, $tagName) === false) {
+ if (! str_contains($xml, $tagName)) {
return $xml;
}
diff --git a/extensions/mentions/src/Formatter/UnparseTagMentions.php b/extensions/mentions/src/Formatter/UnparseTagMentions.php
index 483e0517b0..06eb0f3735 100644
--- a/extensions/mentions/src/Formatter/UnparseTagMentions.php
+++ b/extensions/mentions/src/Formatter/UnparseTagMentions.php
@@ -9,7 +9,7 @@
namespace Flarum\Mentions\Formatter;
-use Flarum\Post\Post;
+use Flarum\Database\AbstractModel;
use Flarum\Tags\Tag;
use s9e\TextFormatter\Utils;
@@ -29,8 +29,8 @@ protected function updateTagMentionTags(mixed $context, string $xml): string
{
return Utils::replaceAttributes($xml, 'TAGMENTION', function (array $attributes) use ($context) {
/** @var Tag|null $tag */
- $tag = (($context && isset($context->getRelations()['mentionsTags'])) || $context instanceof Post)
- ? $context->mentionsTags->find($attributes['id'])
+ $tag = ($context instanceof AbstractModel && $context->isRelation('mentionsTags'))
+ ? $context->mentionsTags->find($attributes['id']) // @phpstan-ignore-line
: Tag::query()->find($attributes['id']);
if ($tag) {
@@ -49,7 +49,7 @@ protected function unparseTagMentionTags(string $xml): string
{
$tagName = 'TAGMENTION';
- if (strpos($xml, $tagName) === false) {
+ if (! str_contains($xml, $tagName)) {
return $xml;
}
diff --git a/extensions/mentions/src/Formatter/UnparseUserMentions.php b/extensions/mentions/src/Formatter/UnparseUserMentions.php
index b7fe68dd92..a278166f9a 100644
--- a/extensions/mentions/src/Formatter/UnparseUserMentions.php
+++ b/extensions/mentions/src/Formatter/UnparseUserMentions.php
@@ -9,8 +9,8 @@
namespace Flarum\Mentions\Formatter;
+use Flarum\Database\AbstractModel;
use Flarum\Locale\TranslatorInterface;
-use Flarum\Post\Post;
use Flarum\User\User;
use s9e\TextFormatter\Utils;
@@ -34,17 +34,13 @@ public function __invoke(mixed $context, string $xml): string
protected function updateUserMentionTags(mixed $context, string $xml): string
{
return Utils::replaceAttributes($xml, 'USERMENTION', function ($attributes) use ($context) {
- $user = (($context && isset($context->getRelations()['mentionsUsers'])) || $context instanceof Post)
- ? $context->mentionsUsers->find($attributes['id'])
+ $user = ($context instanceof AbstractModel && $context->isRelation('mentionsUsers'))
+ ? $context->mentionsUsers->find($attributes['id']) // @phpstan-ignore-line
: User::find($attributes['id']);
- if ($user) {
- $attributes['displayname'] = $user->display_name;
- } else {
- $attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
- }
+ $attributes['displayname'] = $user?->display_name ?? $this->translator->trans('core.lib.username.deleted_text');
- if (strpos($attributes['displayname'], '"#') !== false) {
+ if (str_contains($attributes['displayname'], '"#')) {
$attributes['displayname'] = preg_replace('/"#[a-z]{0,3}[0-9]+/', '_', $attributes['displayname']);
}
@@ -59,7 +55,7 @@ protected function unparseUserMentionTags(string $xml): string
{
$tagName = 'USERMENTION';
- if (strpos($xml, $tagName) === false) {
+ if (! str_contains($xml, $tagName)) {
return $xml;
}
diff --git a/extensions/mentions/src/Notification/GroupMentionedBlueprint.php b/extensions/mentions/src/Notification/GroupMentionedBlueprint.php
index 50d9b5824d..327894ee9a 100644
--- a/extensions/mentions/src/Notification/GroupMentionedBlueprint.php
+++ b/extensions/mentions/src/Notification/GroupMentionedBlueprint.php
@@ -11,12 +11,13 @@
use Flarum\Database\AbstractModel;
use Flarum\Locale\TranslatorInterface;
+use Flarum\Notification\AlertableInterface;
use Flarum\Notification\Blueprint\BlueprintInterface;
use Flarum\Notification\MailableInterface;
use Flarum\Post\Post;
use Flarum\User\User;
-class GroupMentionedBlueprint implements BlueprintInterface, MailableInterface
+class GroupMentionedBlueprint implements BlueprintInterface, AlertableInterface, MailableInterface
{
public function __construct(
public Post $post
@@ -38,9 +39,11 @@ public function getData(): mixed
return null;
}
- public function getEmailView(): string|array
+ public function getEmailViews(): array
{
- return ['text' => 'flarum-mentions::emails.groupMentioned'];
+ return [
+ 'text' => 'flarum-mentions::emails.plain.groupMentioned',
+ 'html' => 'flarum-mentions::emails.html.groupMentioned', ];
}
public function getEmailSubject(TranslatorInterface $translator): string
diff --git a/extensions/mentions/src/Notification/PostMentionedBlueprint.php b/extensions/mentions/src/Notification/PostMentionedBlueprint.php
index 465ee57ba9..71e00055d8 100644
--- a/extensions/mentions/src/Notification/PostMentionedBlueprint.php
+++ b/extensions/mentions/src/Notification/PostMentionedBlueprint.php
@@ -11,12 +11,13 @@
use Flarum\Database\AbstractModel;
use Flarum\Locale\TranslatorInterface;
+use Flarum\Notification\AlertableInterface;
use Flarum\Notification\Blueprint\BlueprintInterface;
use Flarum\Notification\MailableInterface;
use Flarum\Post\Post;
use Flarum\User\User;
-class PostMentionedBlueprint implements BlueprintInterface, MailableInterface
+class PostMentionedBlueprint implements BlueprintInterface, AlertableInterface, MailableInterface
{
public function __construct(
public Post $post,
@@ -39,9 +40,12 @@ public function getData(): array
return ['replyNumber' => (int) $this->reply->number];
}
- public function getEmailView(): string|array
+ public function getEmailViews(): array
{
- return ['text' => 'flarum-mentions::emails.postMentioned'];
+ return [
+ 'text' => 'flarum-mentions::emails.plain.postMentioned',
+ 'html' => 'flarum-mentions::emails.html.postMentioned',
+ ];
}
public function getEmailSubject(TranslatorInterface $translator): string
diff --git a/extensions/mentions/src/Notification/UserMentionedBlueprint.php b/extensions/mentions/src/Notification/UserMentionedBlueprint.php
index 1555693384..6c90038dcf 100644
--- a/extensions/mentions/src/Notification/UserMentionedBlueprint.php
+++ b/extensions/mentions/src/Notification/UserMentionedBlueprint.php
@@ -11,12 +11,13 @@
use Flarum\Database\AbstractModel;
use Flarum\Locale\TranslatorInterface;
+use Flarum\Notification\AlertableInterface;
use Flarum\Notification\Blueprint\BlueprintInterface;
use Flarum\Notification\MailableInterface;
use Flarum\Post\Post;
use Flarum\User\User;
-class UserMentionedBlueprint implements BlueprintInterface, MailableInterface
+class UserMentionedBlueprint implements BlueprintInterface, AlertableInterface, MailableInterface
{
public function __construct(
public Post $post
@@ -38,9 +39,12 @@ public function getData(): mixed
return null;
}
- public function getEmailView(): string|array
+ public function getEmailViews(): array
{
- return ['text' => 'flarum-mentions::emails.userMentioned'];
+ return [
+ 'text' => 'flarum-mentions::emails.plain.userMentioned',
+ 'html' => 'flarum-mentions::emails.html.userMentioned'
+ ];
}
public function getEmailSubject(TranslatorInterface $translator): string
diff --git a/extensions/mentions/tests/integration/api/GroupMentionsTest.php b/extensions/mentions/tests/integration/api/GroupMentionsTest.php
index f4c60b487b..2efa17744c 100644
--- a/extensions/mentions/tests/integration/api/GroupMentionsTest.php
+++ b/extensions/mentions/tests/integration/api/GroupMentionsTest.php
@@ -10,11 +10,14 @@
namespace Flarum\Mentions\Tests\integration\api;
use Carbon\Carbon;
+use Flarum\Discussion\Discussion;
use Flarum\Group\Group;
use Flarum\Post\CommentPost;
+use Flarum\Post\Post;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Flarum\User\User;
+use PHPUnit\Framework\Attributes\Test;
class GroupMentionsTest extends TestCase
{
@@ -30,14 +33,14 @@ protected function setUp(): void
$this->extension('flarum-mentions');
$this->prepareDatabase([
- 'users' => [
+ User::class => [
['id' => 3, 'username' => 'potato', 'email' => 'potato@machine.local', 'is_email_confirmed' => 1],
['id' => 4, 'username' => 'toby', 'email' => 'toby@machine.local', 'is_email_confirmed' => 1],
],
- 'discussions' => [
+ Discussion::class => [
['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 2],
],
- 'posts' => [
+ Post::class => [
['id' => 4, 'number' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '