Update for composer branch

This commit is contained in:
Toby Zerner 2015-10-11 13:02:57 +10:30
parent 36607a92cc
commit ccda85cc29
29 changed files with 1423 additions and 345 deletions

View File

@ -2,3 +2,5 @@
composer.phar
.DS_Store
Thumbs.db
bower_components
node_modules

View File

@ -9,6 +9,17 @@
* file that was distributed with this source code.
*/
require __DIR__.'/vendor/autoload.php';
use Flarum\Mentions\Listener;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\View\Factory;
return 'Flarum\Mentions\Extension';
return function (Dispatcher $events, Factory $views) {
$events->subscribe(Listener\AddClientAssets::class);
$events->subscribe(Listener\AddPostMentionedByRelationship::class);
$events->subscribe(Listener\FormatPostMentions::class);
$events->subscribe(Listener\FormatUserMentions::class);
$events->subscribe(Listener\UpdatePostMentionsMetadata::class);
$events->subscribe(Listener\UpdateUserMentionsMetadata::class);
$views->addNamespace('flarum-mentions', __DIR__.'/views');
};

View File

@ -1,10 +1,34 @@
{
"name": "flarum/mentions",
"description": "Mention and reply to specific posts and users.",
"type": "flarum-extension",
"license": "MIT",
"authors": [
{
"name": "Toby Zerner",
"email": "toby.zerner@gmail.com"
}
],
"support": {
"issues": "https://github.com/flarum/core/issues",
"source": "https://github.com/flarum/mentions"
},
"require": {
"flarum/core": "^0.1.0-beta.3"
},
"autoload": {
"psr-4": {
"Flarum\\Mentions\\": "src/"
}
},
"scripts": {
"style": "phpcs --standard=PSR2 -np src"
"extra": {
"flarum-extension": {
"title": "Mentions",
"icon": {
"name": "at",
"backgroundColor": "#539EC1",
"color": "#fff"
}
}
}
}

View File

@ -1,25 +0,0 @@
{
"name": "mentions",
"title": "Mentions",
"description": "Mention and reply to specific posts and users.",
"keywords": ["discussions"],
"version": "0.1.0-beta.2",
"author": {
"name": "Toby Zerner",
"email": "toby@flarum.org",
"homepage": "http://tobyzerner.com"
},
"license": "MIT",
"require": {
"flarum": ">=0.1.0-beta.2"
},
"support": {
"source": "https://github.com/flarum/mentions",
"issues": "https://github.com/flarum/core/issues"
},
"icon": {
"name": "at",
"backgroundColor": "#539EC1",
"color": "#fff"
}
}

View File

@ -1,4 +0,0 @@
bower_components
node_modules
mithril.js
dist

View File

@ -2,7 +2,7 @@ var gulp = require('flarum-gulp');
gulp({
modules: {
'mentions': 'src/**/*.js'
'flarum/mentions': 'src/**/*.js'
},
files: [
'bower_components/textarea-caret-position/index.js'

View File

@ -0,0 +1,955 @@
/* jshint browser: true */
(function () {
// The properties that we copy into a mirrored div.
// Note that some browsers, such as Firefox,
// do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
// so we have to do every single property specifically.
var properties = [
'direction', // RTL support
'boxSizing',
'width', // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
'height',
'overflowX',
'overflowY', // copy the scrollbar for IE
'borderTopWidth',
'borderRightWidth',
'borderBottomWidth',
'borderLeftWidth',
'borderStyle',
'paddingTop',
'paddingRight',
'paddingBottom',
'paddingLeft',
// https://developer.mozilla.org/en-US/docs/Web/CSS/font
'fontStyle',
'fontVariant',
'fontWeight',
'fontStretch',
'fontSize',
'fontSizeAdjust',
'lineHeight',
'fontFamily',
'textAlign',
'textTransform',
'textIndent',
'textDecoration', // might not make a difference, but better be safe
'letterSpacing',
'wordSpacing',
'tabSize',
'MozTabSize'
];
var isFirefox = window.mozInnerScreenX != null;
function getCaretCoordinates(element, position) {
// mirrored div
var div = document.createElement('div');
div.id = 'input-textarea-caret-position-mirror-div';
document.body.appendChild(div);
var style = div.style;
var computed = window.getComputedStyle? getComputedStyle(element) : element.currentStyle; // currentStyle for IE < 9
// default textarea styles
style.whiteSpace = 'pre-wrap';
if (element.nodeName !== 'INPUT')
style.wordWrap = 'break-word'; // only for textarea-s
// position off-screen
style.position = 'absolute'; // required to return coordinates properly
style.visibility = 'hidden'; // not 'display: none' because we want rendering
// transfer the element's properties to the div
properties.forEach(function (prop) {
style[prop] = computed[prop];
});
if (isFirefox) {
// Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
if (element.scrollHeight > parseInt(computed.height))
style.overflowY = 'scroll';
} else {
style.overflow = 'hidden'; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
}
div.textContent = element.value.substring(0, position);
// the second special handling for input type="text" vs textarea: spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
if (element.nodeName === 'INPUT')
div.textContent = div.textContent.replace(/\s/g, "\u00a0");
var span = document.createElement('span');
// Wrapping must be replicated *exactly*, including when a long word gets
// onto the next line, with whitespace at the end of the line before (#7).
// The *only* reliable way to do that is to copy the *entire* rest of the
// textarea's content into the <span> created at the caret position.
// for inputs, just '.' would be enough, but why bother?
span.textContent = element.value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all
div.appendChild(span);
var coordinates = {
top: span.offsetTop + parseInt(computed['borderTopWidth']),
left: span.offsetLeft + parseInt(computed['borderLeftWidth'])
};
document.body.removeChild(div);
return coordinates;
}
if (typeof module != "undefined" && typeof module.exports != "undefined") {
module.exports = getCaretCoordinates;
} else {
window.getCaretCoordinates = getCaretCoordinates;
}
}());
;System.register('flarum/mentions/addComposerAutocomplete', ['flarum/extend', 'flarum/components/ComposerBody', 'flarum/helpers/avatar', 'flarum/helpers/username', 'flarum/helpers/highlight', 'flarum/utils/string', 'flarum/mentions/components/AutocompleteDropdown'], function (_export) {
/*global getCaretCoordinates*/
'use strict';
var extend, ComposerBody, avatar, usernameHelper, highlight, truncate, AutocompleteDropdown;
_export('default', addComposerAutocomplete);
function addComposerAutocomplete() {
extend(ComposerBody.prototype, 'config', function (original, isInitialized) {
if (isInitialized) return;
var composer = this;
var $container = $('<div class="ComposerBody-mentionsDropdownContainer"></div>');
var dropdown = new AutocompleteDropdown({ items: [] });
var $textarea = this.$('textarea');
var searched = [];
var mentionStart = undefined;
var typed = undefined;
var searchTimeout = undefined;
var applySuggestion = function applySuggestion(replacement) {
var insert = replacement + ' ';
var content = composer.content();
composer.editor.setValue(content.substring(0, mentionStart - 1) + insert + content.substr($textarea[0].selectionStart));
var index = mentionStart - 1 + insert.length;
composer.editor.setSelectionRange(index, index);
dropdown.hide();
};
$textarea.after($container).on('keydown', dropdown.navigate.bind(dropdown)).on('click keyup', function (e) {
var _this = this;
// Up, down, enter, tab, escape, left, right.
if ([9, 13, 27, 40, 38, 37, 39].indexOf(e.which) !== -1) return;
var cursor = this.selectionStart;
if (this.selectionEnd - cursor > 0) return;
// Search backwards from the cursor for an '@' symbol, without any
// intervening whitespace. If we find one, we will want to show the
// autocomplete dropdown!
var value = this.value;
mentionStart = 0;
for (var i = cursor - 1; i >= 0; i--) {
var character = value.substr(i, 1);
if (/\s/.test(character)) break;
if (character === '@') {
mentionStart = i + 1;
break;
}
}
dropdown.hide();
dropdown.active = false;
if (mentionStart) {
(function () {
typed = value.substring(mentionStart, cursor).toLowerCase();
var makeSuggestion = function makeSuggestion(user, replacement, content) {
var className = arguments.length <= 3 || arguments[3] === undefined ? '' : arguments[3];
var username = usernameHelper(user);
if (typed) {
username.children[0] = highlight(username.children[0], typed);
}
return m(
'button',
{ className: 'PostPreview ' + className,
onclick: function () {
return applySuggestion(replacement);
},
onmouseenter: function () {
dropdown.setIndex($(this).parent().index());
} },
m(
'span',
{ className: 'PostPreview-content' },
avatar(user),
username,
' ',
' ',
content
)
);
};
var buildSuggestions = function buildSuggestions() {
var suggestions = [];
// If the user is replying to a discussion, or if they are editing a
// post, then we can suggest other posts in the discussion to mention.
// We will add the 5 most recent comments in the discussion which
// match any username characters that have been typed.
var composerPost = composer.props.post;
var discussion = composerPost && composerPost.discussion() || composer.props.discussion;
if (discussion) {
discussion.posts().filter(function (post) {
return post && post.contentType() === 'comment' && (!composerPost || post.number() < composerPost.number());
}).sort(function (a, b) {
return b.time() - a.time();
}).filter(function (post) {
var user = post.user();
return user && user.username().toLowerCase().substr(0, typed.length) === typed;
}).splice(0, 5).forEach(function (post) {
var user = post.user();
suggestions.push(makeSuggestion(user, '@' + user.username() + '#' + post.id(), [app.trans('flarum-mentions.forum.reply_to_post', { number: post.number() }), ' — ', truncate(post.contentPlain(), 200)], 'MentionsDropdown-post'));
});
}
// If the user has started to type a username, then suggest users
// matching that username.
if (typed) {
app.store.all('users').forEach(function (user) {
if (user.username().toLowerCase().substr(0, typed.length) !== typed) return;
suggestions.push(makeSuggestion(user, '@' + user.username(), '', 'MentionsDropdown-user'));
});
}
if (suggestions.length) {
dropdown.props.items = suggestions;
m.render($container[0], dropdown.render());
dropdown.show();
var coordinates = getCaretCoordinates(_this, mentionStart);
var width = dropdown.$().outerWidth();
var height = dropdown.$().outerHeight();
var _parent = dropdown.$().offsetParent();
var left = coordinates.left;
var _top = coordinates.top + 15;
if (_top + height > _parent.height()) {
_top = coordinates.top - height - 15;
}
if (left + width > _parent.width()) {
left = _parent.width() - width;
}
dropdown.show(left, _top);
}
};
buildSuggestions();
dropdown.setIndex(0);
dropdown.$().scrollTop(0);
dropdown.active = true;
clearTimeout(searchTimeout);
if (typed) {
searchTimeout = setTimeout(function () {
var typedLower = typed.toLowerCase();
if (searched.indexOf(typedLower) === -1) {
app.store.find('users', { q: typed, page: { limit: 5 } }).then(function () {
if (dropdown.active) buildSuggestions();
});
searched.push(typedLower);
}
}, 250);
}
})();
}
});
});
}
return {
setters: [function (_flarumExtend) {
extend = _flarumExtend.extend;
}, function (_flarumComponentsComposerBody) {
ComposerBody = _flarumComponentsComposerBody['default'];
}, function (_flarumHelpersAvatar) {
avatar = _flarumHelpersAvatar['default'];
}, function (_flarumHelpersUsername) {
usernameHelper = _flarumHelpersUsername['default'];
}, function (_flarumHelpersHighlight) {
highlight = _flarumHelpersHighlight['default'];
}, function (_flarumUtilsString) {
truncate = _flarumUtilsString.truncate;
}, function (_flarumMentionsComponentsAutocompleteDropdown) {
AutocompleteDropdown = _flarumMentionsComponentsAutocompleteDropdown['default'];
}],
execute: function () {}
};
});;System.register('flarum/mentions/addMentionedByList', ['flarum/extend', 'flarum/Model', 'flarum/models/Post', 'flarum/components/CommentPost', 'flarum/components/PostPreview', 'flarum/helpers/punctuateSeries', 'flarum/helpers/username', 'flarum/helpers/icon'], function (_export) {
'use strict';
var extend, Model, Post, CommentPost, PostPreview, punctuateSeries, username, icon;
_export('default', addMentionedByList);
function addMentionedByList() {
Post.prototype.mentionedBy = Model.hasMany('mentionedBy');
extend(CommentPost.prototype, 'footerItems', function (items) {
var _this = this;
var post = this.props.post;
var replies = post.mentionedBy();
if (replies && replies.length) {
var _ret = (function () {
// If there is only one reply, and it's adjacent to this post, we don't
// really need to show the list.
if (replies.length === 1 && replies[0].number() === post.number() + 1) {
return {
v: undefined
};
}
var hidePreview = function hidePreview() {
_this.$('.Post-mentionedBy-preview').removeClass('in').one('transitionend', function () {
$(this).hide();
});
};
var config = function config(element, isInitialized) {
if (isInitialized) return;
var $this = $(element);
var timeout = undefined;
var $preview = $('<ul class="Dropdown-menu Post-mentionedBy-preview fade"/>');
$this.append($preview);
$this.children().hover(function () {
clearTimeout(timeout);
timeout = setTimeout(function () {
if (!$preview.hasClass('in') && $preview.is(':visible')) return;
// When the user hovers their mouse over the list of people who have
// replied to the post, render a list of reply previews into a
// popup.
m.render($preview[0], replies.map(function (reply) {
return m(
'li',
{ 'data-number': reply.number() },
PostPreview.component({
post: reply,
onclick: hidePreview
})
);
}));
$preview.show();
setTimeout(function () {
return $preview.off('transitionend').addClass('in');
});
}, 500);
}, function () {
clearTimeout(timeout);
timeout = setTimeout(hidePreview, 250);
});
// Whenever the user hovers their mouse over a particular name in the
// list of repliers, highlight the corresponding post in the preview
// popup.
$this.find('.Post-mentionedBy-summary a').hover(function () {
$preview.find('[data-number="' + $(this).data('number') + '"]').addClass('active');
}, function () {
$preview.find('[data-number]').removeClass('active');
});
};
// 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.
var used = [];
var repliers = replies.filter(function (reply) {
var user = reply.user();
var id = user && user.id();
if (used.indexOf(id) === -1) {
used.push(id);
return true;
}
});
var names = repliers.sort(function (a) {
return a === app.session.user ? -1 : 1;
}).map(function (reply) {
var user = reply.user();
return m(
'a',
{ href: app.route.post(reply),
config: m.route,
onclick: hidePreview,
'data-number': reply.number() },
app.session.user === user ? app.trans('flarum-mentions.forum.you') : username(user)
);
});
items.add('replies', m(
'div',
{ className: 'Post-mentionedBy', config: config },
m(
'span',
{ className: 'Post-mentionedBy-summary' },
icon('reply'),
app.trans('flarum-mentions.forum.post_mentioned_by', {
count: names.length,
users: punctuateSeries(names)
})
)
));
})();
if (typeof _ret === 'object') return _ret.v;
}
});
}
return {
setters: [function (_flarumExtend) {
extend = _flarumExtend.extend;
}, function (_flarumModel) {
Model = _flarumModel['default'];
}, function (_flarumModelsPost) {
Post = _flarumModelsPost['default'];
}, function (_flarumComponentsCommentPost) {
CommentPost = _flarumComponentsCommentPost['default'];
}, function (_flarumComponentsPostPreview) {
PostPreview = _flarumComponentsPostPreview['default'];
}, function (_flarumHelpersPunctuateSeries) {
punctuateSeries = _flarumHelpersPunctuateSeries['default'];
}, function (_flarumHelpersUsername) {
username = _flarumHelpersUsername['default'];
}, function (_flarumHelpersIcon) {
icon = _flarumHelpersIcon['default'];
}],
execute: function () {}
};
});;System.register('flarum/mentions/addPostMentionPreviews', ['flarum/extend', 'flarum/components/CommentPost', 'flarum/components/PostPreview', 'flarum/components/LoadingIndicator'], function (_export) {
'use strict';
var extend, CommentPost, PostPreview, LoadingIndicator;
_export('default', addPostMentionPreviews);
function addPostMentionPreviews() {
extend(CommentPost.prototype, 'config', function () {
var contentHtml = this.props.post.contentHtml();
if (contentHtml === this.oldPostContentHtml || this.isEditing()) return;
this.oldPostContentHtml = contentHtml;
var parentPost = this.props.post;
var $parentPost = this.$();
this.$('.UserMention, .PostMention').each(function () {
m.route.call(this, this, false, {}, { attrs: { href: this.getAttribute('href') } });
});
this.$('.PostMention').each(function () {
var $this = $(this);
var id = $this.data('id');
var timeout = undefined;
// Wrap the mention link in a wrapper element so that we can insert a
// preview popup as its sibling and relatively position it.
var $preview = $('<ul class="Dropdown-menu PostMention-preview fade"/>');
$parentPost.append($preview);
var getPostElement = function getPostElement() {
return $('.PostStream-item[data-id="' + id + '"]');
};
var showPreview = function showPreview() {
// When the user hovers their mouse over the mention, look for the
// post that it's referring to in the stream, and determine if it's
// in the viewport. If it is, we will "pulsate" it.
var $post = getPostElement();
var visible = false;
if ($post.length) {
var _top = $post.offset().top;
var scrollTop = window.pageYOffset;
if (_top > scrollTop && _top + $post.height() < scrollTop + $(window).height()) {
$post.addClass('pulsate');
visible = true;
}
}
// Otherwise, we will show a popup preview of the post. If the post
// hasn't yet been loaded, we will need to do that.
if (!visible) {
(function () {
// Position the preview so that it appears above the mention.
// (The offsetParent should be .Post-body.)
var positionPreview = function positionPreview() {
$preview.show().css('top', $this.offset().top - $parentPost.offset().top - $preview.outerHeight(true)).css('left', $this.offsetParent().offset().left - $parentPost.offset().left).css('max-width', $this.offsetParent().width());
};
var showPost = function showPost(post) {
var discussion = post.discussion();
m.render($preview[0], [discussion !== parentPost.discussion() ? m(
'li',
null,
m(
'span',
{ className: 'PostMention-preview-discussion' },
discussion.title()
)
) : '', m(
'li',
null,
PostPreview.component({ post: post })
)]);
positionPreview();
};
var post = app.store.getById('posts', id);
if (post && post.discussion()) {
showPost(post);
} else {
m.render($preview[0], LoadingIndicator.component());
app.store.find('posts', id).then(showPost);
positionPreview();
}
setTimeout(function () {
return $preview.off('transitionend').addClass('in');
});
})();
}
};
var hidePreview = function hidePreview() {
getPostElement().removeClass('pulsate');
if ($preview.hasClass('in')) {
$preview.removeClass('in').one('transitionend', function () {
return $preview.hide();
});
}
};
$this.on('touchstart', function (e) {
return e.preventDefault();
});
$this.add($preview).hover(function () {
clearTimeout(timeout);
timeout = setTimeout(showPreview, 250);
}, function () {
clearTimeout(timeout);
getPostElement().removeClass('pulsate');
timeout = setTimeout(hidePreview, 250);
}).on('touchend', function (e) {
showPreview();
e.stopPropagation();
});
$(document).on('touchend', hidePreview);
});
});
}
return {
setters: [function (_flarumExtend) {
extend = _flarumExtend.extend;
}, function (_flarumComponentsCommentPost) {
CommentPost = _flarumComponentsCommentPost['default'];
}, function (_flarumComponentsPostPreview) {
PostPreview = _flarumComponentsPostPreview['default'];
}, function (_flarumComponentsLoadingIndicator) {
LoadingIndicator = _flarumComponentsLoadingIndicator['default'];
}],
execute: function () {}
};
});;System.register('flarum/mentions/addPostReplyAction', ['flarum/extend', 'flarum/components/Button', 'flarum/components/CommentPost', 'flarum/utils/DiscussionControls'], function (_export) {
'use strict';
var extend, Button, CommentPost, DiscussionControls;
return {
setters: [function (_flarumExtend) {
extend = _flarumExtend.extend;
}, function (_flarumComponentsButton) {
Button = _flarumComponentsButton['default'];
}, function (_flarumComponentsCommentPost) {
CommentPost = _flarumComponentsCommentPost['default'];
}, function (_flarumUtilsDiscussionControls) {
DiscussionControls = _flarumUtilsDiscussionControls['default'];
}],
execute: function () {
_export('default', function () {
extend(CommentPost.prototype, 'actionItems', function (items) {
var post = this.props.post;
if (post.isHidden() || app.session.user && !post.discussion().canReply()) return;
function insertMention(component, quote) {
var mention = '@' + post.user().username() + '#' + post.id() + ' ';
// If the composer is empty, then assume we're starting a new reply.
// In which case we don't want the user to have to confirm if they
// close the composer straight away.
if (!component.content()) {
component.props.originalContent = mention;
}
component.editor.insertAtCursor((component.editor.getSelectionRange()[0] > 0 ? '\n\n' : '') + (quote ? '> ' + mention + quote.trim().replace(/\n/g, '\n> ') + '\n\n' : mention));
}
items.add('reply', Button.component({
className: 'Button Button--link',
children: app.trans('flarum-mentions.forum.reply_link'),
onclick: function onclick() {
var quote = window.getSelection().toString();
var component = app.composer.component;
if (component && component.props.post && component.props.post.discussion() === post.discussion()) {
insertMention(component, quote);
} else {
DiscussionControls.replyAction.call(post.discussion()).then(function (newComponent) {
return insertMention(newComponent, quote);
});
}
}
}));
});
});
}
};
});;System.register('flarum/mentions/main', ['flarum/extend', 'flarum/app', 'flarum/components/NotificationGrid', 'flarum/utils/string', 'flarum/mentions/addPostMentionPreviews', 'flarum/mentions/addMentionedByList', 'flarum/mentions/addPostReplyAction', 'flarum/mentions/addComposerAutocomplete', 'flarum/mentions/components/PostMentionedNotification', 'flarum/mentions/components/UserMentionedNotification'], function (_export) {
'use strict';
var extend, app, NotificationGrid, getPlainContent, addPostMentionPreviews, addMentionedByList, addPostReplyAction, addComposerAutocomplete, PostMentionedNotification, UserMentionedNotification;
return {
setters: [function (_flarumExtend) {
extend = _flarumExtend.extend;
}, function (_flarumApp) {
app = _flarumApp['default'];
}, function (_flarumComponentsNotificationGrid) {
NotificationGrid = _flarumComponentsNotificationGrid['default'];
}, function (_flarumUtilsString) {
getPlainContent = _flarumUtilsString.getPlainContent;
}, function (_flarumMentionsAddPostMentionPreviews) {
addPostMentionPreviews = _flarumMentionsAddPostMentionPreviews['default'];
}, function (_flarumMentionsAddMentionedByList) {
addMentionedByList = _flarumMentionsAddMentionedByList['default'];
}, function (_flarumMentionsAddPostReplyAction) {
addPostReplyAction = _flarumMentionsAddPostReplyAction['default'];
}, function (_flarumMentionsAddComposerAutocomplete) {
addComposerAutocomplete = _flarumMentionsAddComposerAutocomplete['default'];
}, function (_flarumMentionsComponentsPostMentionedNotification) {
PostMentionedNotification = _flarumMentionsComponentsPostMentionedNotification['default'];
}, function (_flarumMentionsComponentsUserMentionedNotification) {
UserMentionedNotification = _flarumMentionsComponentsUserMentionedNotification['default'];
}],
execute: function () {
app.initializers.add('flarum-mentions', function () {
// For every mention of a post inside a post's content, set up a hover handler
// that shows a preview of the mentioned post.
addPostMentionPreviews();
// In the footer of each post, show information about who has replied (i.e.
// who the post has been mentioned by).
addMentionedByList();
// Add a 'reply' control to the footer of each post. When clicked, it will
// open up the composer and add a post mention to its contents.
addPostReplyAction();
// After typing '@' in the composer, show a dropdown suggesting a bunch of
// posts or users that the user could mention.
addComposerAutocomplete();
app.notificationComponents.postMentioned = PostMentionedNotification;
app.notificationComponents.userMentioned = UserMentionedNotification;
// Add notification preferences.
extend(NotificationGrid.prototype, 'notificationTypes', function (items) {
items.add('postMentioned', {
name: 'postMentioned',
icon: 'reply',
label: app.trans('flarum-mentions.forum.notify_post_mentioned')
});
items.add('userMentioned', {
name: 'userMentioned',
icon: 'at',
label: app.trans('flarum-mentions.forum.notify_user_mentioned')
});
});
getPlainContent.removeSelectors.push('a.PostMention');
});
}
};
});;System.register('flarum/mentions/components/AutocompleteDropdown', ['flarum/Component'], function (_export) {
'use strict';
var Component, AutocompleteDropdown;
return {
setters: [function (_flarumComponent) {
Component = _flarumComponent['default'];
}],
execute: function () {
AutocompleteDropdown = (function (_Component) {
babelHelpers.inherits(AutocompleteDropdown, _Component);
function AutocompleteDropdown() {
babelHelpers.classCallCheck(this, AutocompleteDropdown);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
babelHelpers.get(Object.getPrototypeOf(AutocompleteDropdown.prototype), 'constructor', this).apply(this, args);
this.active = false;
this.index = 0;
this.keyWasJustPressed = false;
}
babelHelpers.createClass(AutocompleteDropdown, [{
key: 'view',
value: function view() {
return m(
'ul',
{ className: 'Dropdown-menu MentionsDropdown' },
this.props.items.map(function (item) {
return m(
'li',
null,
item
);
})
);
}
}, {
key: 'show',
value: function show(left, top) {
this.$().show().css({
left: left + 'px',
top: top + 'px'
});
this.active = true;
}
}, {
key: 'hide',
value: function hide() {
this.$().hide();
this.active = false;
}
}, {
key: 'navigate',
value: function navigate(e) {
var _this = this;
if (!this.active) return;
switch (e.which) {
case 40:case 38:
// Down/Up
this.keyWasJustPressed = true;
this.setIndex(this.index + (e.which === 40 ? 1 : -1), true);
clearTimeout(this.keyWasJustPressedTimeout);
this.keyWasJustPressedTimeout = setTimeout(function () {
return _this.keyWasJustPressed = false;
}, 500);
e.preventDefault();
break;
case 13:case 9:
// Enter/Tab
this.$('li').eq(this.index).find('button').click();
e.preventDefault();
break;
case 27:
// Escape
this.hide();
e.stopPropagation();
e.preventDefault();
break;
default:
// no default
}
}
}, {
key: 'setIndex',
value: function setIndex(index, scrollToItem) {
if (this.keyWasJustPressed && !scrollToItem) return;
var $dropdown = this.$();
var $items = $dropdown.find('li');
var rangedIndex = index;
if (rangedIndex < 0) {
rangedIndex = $items.length - 1;
} else if (rangedIndex >= $items.length) {
rangedIndex = 0;
}
this.index = rangedIndex;
var $item = $items.removeClass('active').eq(rangedIndex).addClass('active');
if (scrollToItem) {
var dropdownScroll = $dropdown.scrollTop();
var dropdownTop = $dropdown.offset().top;
var dropdownBottom = dropdownTop + $dropdown.outerHeight();
var itemTop = $item.offset().top;
var itemBottom = itemTop + $item.outerHeight();
var scrollTop = undefined;
if (itemTop < dropdownTop) {
scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt($dropdown.css('padding-top'), 10);
} else if (itemBottom > dropdownBottom) {
scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt($dropdown.css('padding-bottom'), 10);
}
if (typeof scrollTop !== 'undefined') {
$dropdown.stop(true).animate({ scrollTop: scrollTop }, 100);
}
}
}
}]);
return AutocompleteDropdown;
})(Component);
_export('default', AutocompleteDropdown);
}
};
});;System.register('flarum/mentions/components/PostMentionedNotification', ['flarum/components/Notification', 'flarum/helpers/username', 'flarum/helpers/punctuate'], function (_export) {
'use strict';
var Notification, username, punctuate, PostMentionedNotification;
return {
setters: [function (_flarumComponentsNotification) {
Notification = _flarumComponentsNotification['default'];
}, function (_flarumHelpersUsername) {
username = _flarumHelpersUsername['default'];
}, function (_flarumHelpersPunctuate) {
punctuate = _flarumHelpersPunctuate['default'];
}],
execute: function () {
PostMentionedNotification = (function (_Notification) {
babelHelpers.inherits(PostMentionedNotification, _Notification);
function PostMentionedNotification() {
babelHelpers.classCallCheck(this, PostMentionedNotification);
babelHelpers.get(Object.getPrototypeOf(PostMentionedNotification.prototype), 'constructor', this).apply(this, arguments);
}
babelHelpers.createClass(PostMentionedNotification, [{
key: 'icon',
value: function icon() {
return 'reply';
}
}, {
key: 'href',
value: function href() {
var notification = this.props.notification;
var post = notification.subject();
var auc = notification.additionalUnreadCount();
var content = notification.content();
return app.route.discussion(post.discussion(), auc ? post.number() : content && content.replyNumber);
}
}, {
key: 'content',
value: function content() {
var notification = this.props.notification;
var auc = notification.additionalUnreadCount();
var user = notification.sender();
return app.trans('flarum-mentions.forum.post_mentioned_notification', {
user: user,
username: auc ? punctuate([username(user), app.trans('flarum-mentions.forum.others', { count: auc })]) : undefined
});
}
}, {
key: 'excerpt',
value: function excerpt() {
return this.props.notification.subject().contentPlain();
}
}]);
return PostMentionedNotification;
})(Notification);
_export('default', PostMentionedNotification);
}
};
});;System.register('flarum/mentions/components/UserMentionedNotification', ['flarum/components/Notification'], function (_export) {
'use strict';
var Notification, UserMentionedNotification;
return {
setters: [function (_flarumComponentsNotification) {
Notification = _flarumComponentsNotification['default'];
}],
execute: function () {
UserMentionedNotification = (function (_Notification) {
babelHelpers.inherits(UserMentionedNotification, _Notification);
function UserMentionedNotification() {
babelHelpers.classCallCheck(this, UserMentionedNotification);
babelHelpers.get(Object.getPrototypeOf(UserMentionedNotification.prototype), 'constructor', this).apply(this, arguments);
}
babelHelpers.createClass(UserMentionedNotification, [{
key: 'icon',
value: function icon() {
return 'at';
}
}, {
key: 'href',
value: function href() {
var post = this.props.notification.subject();
return app.route.discussion(post.discussion(), post.number());
}
}, {
key: 'content',
value: function content() {
var user = this.props.notification.sender();
return app.trans('flarum-mentions.forum.user_mentioned_notification', { user: user });
}
}, {
key: 'excerpt',
value: function excerpt() {
return this.props.notification.subject().contentPlain();
}
}]);
return UserMentionedNotification;
})(Notification);
_export('default', UserMentionedNotification);
}
};
});

View File

@ -7,7 +7,7 @@ import usernameHelper from 'flarum/helpers/username';
import highlight from 'flarum/helpers/highlight';
import { truncate } from 'flarum/utils/string';
import AutocompleteDropdown from 'mentions/components/AutocompleteDropdown';
import AutocompleteDropdown from 'flarum/mentions/components/AutocompleteDropdown';
export default function addComposerAutocomplete() {
extend(ComposerBody.prototype, 'config', function(original, isInitialized) {
@ -79,7 +79,7 @@ export default function addComposerAutocomplete() {
}}>
<span className="PostPreview-content">
{avatar(user)}
{username}{' '}
{username} {' '}
{content}
</span>
</button>
@ -108,7 +108,7 @@ export default function addComposerAutocomplete() {
const user = post.user();
suggestions.push(
makeSuggestion(user, '@' + user.username() + '#' + post.id(), [
app.trans('mentions.reply_to_post', {number: post.number()}), ' — ',
app.trans('flarum-mentions.forum.reply_to_post', {number: post.number()}), ' — ',
truncate(post.contentPlain(), 200)
], 'MentionsDropdown-post')
);

View File

@ -3,7 +3,7 @@ import Model from 'flarum/Model';
import Post from 'flarum/models/Post';
import CommentPost from 'flarum/components/CommentPost';
import PostPreview from 'flarum/components/PostPreview';
import punctuate from 'flarum/helpers/punctuate';
import punctuateSeries from 'flarum/helpers/punctuateSeries';
import username from 'flarum/helpers/username';
import icon from 'flarum/helpers/icon';
@ -91,7 +91,7 @@ export default function addMentionedByList() {
config={m.route}
onclick={hidePreview}
data-number={reply.number()}>
{app.session.user === user ? app.trans('mentions.you') : username(user)}
{app.session.user === user ? app.trans('flarum-mentions.forum.you') : username(user)}
</a>
);
});
@ -100,9 +100,9 @@ export default function addMentionedByList() {
<div className="Post-mentionedBy" config={config}>
<span className="Post-mentionedBy-summary">
{icon('reply')}
{app.trans('mentions.post_mentioned_by', {
{app.trans('flarum-mentions.forum.post_mentioned_by', {
count: names.length,
users: punctuate(names)
users: punctuateSeries(names)
})}
</span>
</div>

View File

@ -30,7 +30,7 @@ export default function() {
items.add('reply',
Button.component({
className: 'Button Button--link',
children: app.trans('mentions.reply_link'),
children: app.trans('flarum-mentions.forum.reply_link'),
onclick: () => {
const quote = window.getSelection().toString();

View File

@ -21,11 +21,11 @@ export default class PostMentionedNotification extends Notification {
const auc = notification.additionalUnreadCount();
const user = notification.sender();
return app.trans('mentions.post_mentioned_notification', {
return app.trans('flarum-mentions.forum.post_mentioned_notification', {
user,
username: auc ? punctuate([
username(user),
app.trans('mentions.others', {count: auc})
app.trans('flarum-mentions.forum.others', {count: auc})
]) : undefined
});
}

View File

@ -14,7 +14,7 @@ export default class UserMentionedNotification extends Notification {
content() {
const user = this.props.notification.sender();
return app.trans('mentions.user_mentioned_notification', {user});
return app.trans('flarum-mentions.forum.user_mentioned_notification', {user});
}
excerpt() {

View File

@ -3,14 +3,14 @@ import app from 'flarum/app';
import NotificationGrid from 'flarum/components/NotificationGrid';
import { getPlainContent } from 'flarum/utils/string';
import addPostMentionPreviews from 'mentions/addPostMentionPreviews';
import addMentionedByList from 'mentions/addMentionedByList';
import addPostReplyAction from 'mentions/addPostReplyAction';
import addComposerAutocomplete from 'mentions/addComposerAutocomplete';
import PostMentionedNotification from 'mentions/components/PostMentionedNotification';
import UserMentionedNotification from 'mentions/components/UserMentionedNotification';
import addPostMentionPreviews from 'flarum/mentions/addPostMentionPreviews';
import addMentionedByList from 'flarum/mentions/addMentionedByList';
import addPostReplyAction from 'flarum/mentions/addPostReplyAction';
import addComposerAutocomplete from 'flarum/mentions/addComposerAutocomplete';
import PostMentionedNotification from 'flarum/mentions/components/PostMentionedNotification';
import UserMentionedNotification from 'flarum/mentions/components/UserMentionedNotification';
app.initializers.add('mentions', function() {
app.initializers.add('flarum-mentions', function() {
// For every mention of a post inside a post's content, set up a hover handler
// that shows a preview of the mentioned post.
addPostMentionPreviews();
@ -35,13 +35,13 @@ app.initializers.add('mentions', function() {
items.add('postMentioned', {
name: 'postMentioned',
icon: 'reply',
label: app.trans('mentions.notify_post_mentioned')
label: app.trans('flarum-mentions.forum.notify_post_mentioned')
});
items.add('userMentioned', {
name: 'userMentioned',
icon: 'at',
label: app.trans('mentions.notify_user_mentioned')
label: app.trans('flarum-mentions.forum.notify_user_mentioned')
});
});

View File

@ -1,10 +0,0 @@
mentions:
reply_to_post: "Reply to #{number}"
post_mentioned_notification: "{username} replied to your post"
others: "{count} others"
user_mentioned_notification: "{username} mentioned you"
post_mentioned_by: "{users} replied to this."
you: You
reply_link: Reply
notify_post_mentioned: Someone replies to my post
notify_user_mentioned: Someone mentions me in a post

View File

@ -8,18 +8,13 @@
* file that was distributed with this source code.
*/
namespace Flarum\Migrations\Mentions;
namespace Flarum\Mentions\Migration;
use Flarum\Database\AbstractMigration;
use Illuminate\Database\Schema\Blueprint;
use Flarum\Migrations\Migration;
class CreateMentionsPostsTable extends Migration
class CreateMentionsPostsTable extends AbstractMigration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$this->schema->create('mentions_posts', function (Blueprint $table) {
@ -29,11 +24,6 @@ class CreateMentionsPostsTable extends Migration
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$this->schema->drop('mentions_posts');

View File

@ -8,18 +8,13 @@
* file that was distributed with this source code.
*/
namespace Flarum\Migrations\Mentions;
namespace Flarum\Mentions\Migration;
use Flarum\Database\AbstractMigration;
use Illuminate\Database\Schema\Blueprint;
use Flarum\Migrations\Migration;
class CreateMentionsUsersTable extends Migration
class CreateMentionsUsersTable extends AbstractMigration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$this->schema->create('mentions_users', function (Blueprint $table) {
@ -29,11 +24,6 @@ class CreateMentionsUsersTable extends Migration
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$this->schema->drop('mentions_users');

View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
# This script compiles the extension so that it can be used in a Flarum
# installation. It should be run from the root directory of the extension.
base=$PWD
composer install --prefer-dist --optimize-autoloader --ignore-platform-reqs --no-dev
cd "${base}/js"
if [ -f bower.json ]; then
bower install
fi
for app in forum admin; do
cd "${base}/js"
if [ -d $app ]; then
cd $app
if [ -f bower.json ]; then
bower install
fi
npm install
gulp --production
fi
done

View File

@ -1,33 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Mentions;
use Flarum\Support\Extension as BaseExtension;
use Illuminate\Events\Dispatcher;
class Extension extends BaseExtension
{
public function listen(Dispatcher $events)
{
$events->subscribe('Flarum\Mentions\Listeners\AddClientAssets');
$events->subscribe('Flarum\Mentions\Listeners\AddModelRelationships');
$events->subscribe('Flarum\Mentions\Listeners\AddApiRelationships');
$events->subscribe('Flarum\Mentions\Listeners\AddUserMentionsFormatter');
$events->subscribe('Flarum\Mentions\Listeners\AddPostMentionsFormatter');
$events->subscribe('Flarum\Mentions\Listeners\UpdateUserMentionsMetadata');
$events->subscribe('Flarum\Mentions\Listeners\UpdatePostMentionsMetadata');
}
public function boot()
{
$this->loadViewsFrom(__DIR__.'/../views', 'mentions');
}
}

View File

@ -0,0 +1,40 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Mentions\Listener;
use Flarum\Event\ConfigureClientView;
use Illuminate\Contracts\Events\Dispatcher;
class AddClientAssets
{
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(ConfigureClientView::class, [$this, 'addAssets']);
}
/**
* @param ConfigureClientView $event
*/
public function addAssets(ConfigureClientView $event)
{
if ($event->isForum()) {
$event->addAssets([
__DIR__.'/../../js/forum/dist/extension.js',
__DIR__.'/../../less/forum/extension.less'
]);
$event->addBootstrapper('flarum/mentions/main');
$event->addTranslations('flarum-mentions.forum');
}
}
}

View File

@ -0,0 +1,104 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Mentions\Listener;
use Flarum\Api\Controller\CreatePostController;
use Flarum\Api\Controller\ListPostsController;
use Flarum\Api\Controller\ShowDiscussionController;
use Flarum\Api\Controller\ShowPostController;
use Flarum\Api\Serializer\PostBasicSerializer;
use Flarum\Core\Post;
use Flarum\Core\User;
use Flarum\Event\ConfigureApiController;
use Flarum\Event\GetApiRelationship;
use Flarum\Event\GetModelRelationship;
use Illuminate\Contracts\Events\Dispatcher;
class AddPostMentionedByRelationship
{
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(GetModelRelationship::class, [$this, 'getModelRelationship']);
$events->listen(GetApiRelationship::class, [$this, 'getApiRelationship']);
$events->listen(ConfigureApiController::class, [$this, 'includeRelationships']);
}
/**
* @param GetModelRelationship $event
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|null
*/
public function getModelRelationship(GetModelRelationship $event)
{
if ($event->isRelationship(Post::class, 'mentionedBy')) {
return $event->model->belongsToMany(Post::class, 'mentions_posts', 'mentions_id', 'post_id', 'mentionedBy');
}
if ($event->isRelationship(Post::class, 'mentionsPosts')) {
return $event->model->belongsToMany(Post::class, 'mentions_posts', 'post_id', 'mentions_id', 'mentionsPosts');
}
if ($event->isRelationship(Post::class, 'mentionsUsers')) {
return $event->model->belongsToMany(User::class, 'mentions_users', 'post_id', 'mentions_id', 'mentionsUsers');
}
}
/**
* @param GetApiRelationship $event
* @return \Flarum\Api\Relationship\HasManyBuilder|null
*/
public function getApiRelationship(GetApiRelationship $event)
{
if ($event->isRelationship(PostBasicSerializer::class, 'mentionedBy')) {
return $event->serializer->hasMany(PostBasicSerializer::class, 'mentionedBy');
}
if ($event->isRelationship(PostBasicSerializer::class, 'mentionsPosts')) {
return $event->serializer->hasMany(PostBasicSerializer::class, 'mentionsPosts');
}
if ($event->isRelationship(PostBasicSerializer::class, 'mentionsUsers')) {
return $event->serializer->hasMany(PostBasicSerializer::class, 'mentionsUsers');
}
}
/**
* @param ConfigureApiController $event
*/
public function includeRelationships(ConfigureApiController $event)
{
if ($event->isController(ShowDiscussionController::class)) {
$event->addInclude([
'posts.mentionedBy',
'posts.mentionedBy.user',
'posts.mentionedBy.discussion'
]);
}
if ($event->isController(ShowPostController::class)
|| $event->isController(ListPostsController::class)) {
$event->addInclude([
'mentionedBy',
'mentionedBy.user',
'mentionedBy.discussion'
]);
}
if ($event->isController(CreatePostController::class)) {
$event->addInclude([
'mentionsPosts',
'mentionsPosts.mentionedBy'
]);
}
}
}

View File

@ -8,19 +8,26 @@
* file that was distributed with this source code.
*/
namespace Flarum\Mentions\Listeners;
namespace Flarum\Mentions\Listener;
use Flarum\Events\FormatterConfigurator;
use Flarum\Core\Posts\CommentPost;
use Flarum\Core\Post\CommentPost;
use Flarum\Event\ConfigureFormatter;
use Illuminate\Contracts\Events\Dispatcher;
class AddPostMentionsFormatter
class FormatPostMentions
{
public function subscribe($events)
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(FormatterConfigurator::class, [$this, 'configure']);
$events->listen(ConfigureFormatter::class, [$this, 'configure']);
}
public function configure(FormatterConfigurator $event)
/**
* @param ConfigureFormatter $event
*/
public function configure(ConfigureFormatter $event)
{
$configurator = $event->configurator;
@ -44,6 +51,10 @@ class AddPostMentionsFormatter
$configurator->Preg->match('/\B@(?<username>[a-z0-9_-]+)#(?<id>\d+)/i', $tagName);
}
/**
* @param $tag
* @return bool
*/
public static function addId($tag)
{
$post = CommentPost::find($tag->getAttribute('id'));

View File

@ -8,30 +8,43 @@
* file that was distributed with this source code.
*/
namespace Flarum\Mentions\Listeners;
namespace Flarum\Mentions\Listener;
use Flarum\Events\FormatterConfigurator;
use Flarum\Events\FormatterRenderer;
use Flarum\Events\FormatterParser;
use Flarum\Core\Users\UserRepository;
use Flarum\Core\Repository\UserRepository;
use Flarum\Event\ConfigureFormatter;
use Flarum\Event\ConfigureFormatterParser;
use Flarum\Event\ConfigureFormatterRenderer;
use Illuminate\Contracts\Events\Dispatcher;
class AddUserMentionsFormatter
class FormatUserMentions
{
/**
* @var UserRepository
*/
protected $users;
/**
* @param UserRepository $users
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
public function subscribe($events)
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(FormatterConfigurator::class, [$this, 'configure']);
$events->listen(FormatterParser::class, [$this, 'parse']);
$events->listen(FormatterRenderer::class, [$this, 'render']);
$events->listen(ConfigureFormatter::class, [$this, 'configure']);
$events->listen(ConfigureFormatterParser::class, [$this, 'parse']);
$events->listen(ConfigureFormatterRenderer::class, [$this, 'render']);
}
public function configure(FormatterConfigurator $event)
/**
* @param ConfigureFormatter $event
*/
public function configure(ConfigureFormatter $event)
{
$configurator = $event->configurator;
@ -50,17 +63,28 @@ class AddUserMentionsFormatter
$configurator->Preg->match('/\B@(?<username>[a-z0-9_-]+)(?!#)/i', $tagName);
}
public function parse(FormatterParser $event)
/**
* @param ConfigureFormatterParser $event
*/
public function parse(ConfigureFormatterParser $event)
{
$event->parser->registeredVars['userRepository'] = $this->users;
}
public function render(FormatterRenderer $event)
/**
* @param ConfigureFormatterRenderer $event
*/
public function render(ConfigureFormatterRenderer $event)
{
// TODO: use URL generator
$event->renderer->setParameter('PROFILE_URL', '/u/');
}
/**
* @param $tag
* @param UserRepository $users
* @return bool
*/
public static function addId($tag, UserRepository $users)
{
if ($id = $users->getIdForUsername($tag->getAttribute('username'))) {

View File

@ -8,33 +8,42 @@
* file that was distributed with this source code.
*/
namespace Flarum\Mentions\Listeners;
namespace Flarum\Mentions\Listener;
use Flarum\Mentions\Notifications\PostMentionedBlueprint;
use Flarum\Core\Notifications\NotificationSyncer;
use Flarum\Events\RegisterNotificationTypes;
use Flarum\Events\PostWasPosted;
use Flarum\Events\PostWasRevised;
use Flarum\Events\PostWasHidden;
use Flarum\Events\PostWasRestored;
use Flarum\Events\PostWasDeleted;
use Flarum\Core\Posts\Post;
use Flarum\Api\Serializer\PostBasicSerializer;
use Flarum\Core\Notification\NotificationSyncer;
use Flarum\Core\Post;
use Flarum\Event\ConfigureNotificationTypes;
use Flarum\Event\PostWasDeleted;
use Flarum\Event\PostWasHidden;
use Flarum\Event\PostWasPosted;
use Flarum\Event\PostWasRestored;
use Flarum\Event\PostWasRevised;
use Flarum\Mentions\Notification\PostMentionedBlueprint;
use Illuminate\Contracts\Events\Dispatcher;
use s9e\TextFormatter\Utils;
class UpdatePostMentionsMetadata
{
/**
* @var NotificationSyncer
*/
protected $notifications;
/**
* @param NotificationSyncer $notifications
*/
public function __construct(NotificationSyncer $notifications)
{
$this->notifications = $notifications;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(RegisterNotificationTypes::class, [$this, 'registerNotificationType']);
$events->listen(ConfigureNotificationTypes::class, [$this, 'addNotificationType']);
$events->listen(PostWasPosted::class, [$this, 'whenPostWasPosted']);
$events->listen(PostWasRevised::class, [$this, 'whenPostWasRevised']);
$events->listen(PostWasHidden::class, [$this, 'whenPostWasHidden']);
@ -42,40 +51,57 @@ class UpdatePostMentionsMetadata
$events->listen(PostWasDeleted::class, [$this, 'whenPostWasDeleted']);
}
public function registerNotificationType(RegisterNotificationTypes $event)
/**
* @param ConfigureNotificationTypes $event
*/
public function addNotificationType(ConfigureNotificationTypes $event)
{
$event->register(
PostMentionedBlueprint::class,
'Flarum\Api\Serializers\PostBasicSerializer',
['alert']
);
$event->add(PostMentionedBlueprint::class, PostBasicSerializer::class, ['alert']);
}
/**
* @param PostWasPosted $event
*/
public function whenPostWasPosted(PostWasPosted $event)
{
$this->replyBecameVisible($event->post);
}
/**
* @param PostWasRevised $event
*/
public function whenPostWasRevised(PostWasRevised $event)
{
$this->replyBecameVisible($event->post);
}
/**
* @param PostWasHidden $event
*/
public function whenPostWasHidden(PostWasHidden $event)
{
$this->replyBecameInvisible($event->post);
}
/**
* @param PostWasRestored $event
*/
public function whenPostWasRestored(PostWasRestored $event)
{
$this->replyBecameVisible($event->post);
}
/**
* @param PostWasDeleted $event
*/
public function whenPostWasDeleted(PostWasDeleted $event)
{
$this->replyBecameInvisible($event->post);
}
/**
* @param Post $reply
*/
protected function replyBecameVisible(Post $reply)
{
$mentioned = Utils::getAttributeValues($reply->parsedContent, 'POSTMENTION', 'id');
@ -83,11 +109,18 @@ class UpdatePostMentionsMetadata
$this->sync($reply, $mentioned);
}
/**
* @param Post $reply
*/
protected function replyBecameInvisible(Post $reply)
{
$this->sync($reply, []);
}
/**
* @param Post $reply
* @param array $mentioned
*/
protected function sync(Post $reply, array $mentioned)
{
$reply->mentionsPosts()->sync($mentioned);

View File

@ -8,34 +8,42 @@
* file that was distributed with this source code.
*/
namespace Flarum\Mentions\Listeners;
namespace Flarum\Mentions\Listener;
use Flarum\Mentions\Notifications\UserMentionedBlueprint;
use Flarum\Core\Notifications\NotificationSyncer;
use Flarum\Events\RegisterNotificationTypes;
use Flarum\Events\PostWasPosted;
use Flarum\Events\PostWasRevised;
use Flarum\Events\PostWasHidden;
use Flarum\Events\PostWasRestored;
use Flarum\Events\PostWasDeleted;
use Flarum\Core\Posts\Post;
use Flarum\Core\Users\User;
use Flarum\Core\Notification\NotificationSyncer;
use Flarum\Core\Post;
use Flarum\Core\User;
use Flarum\Event\ConfigureNotificationTypes;
use Flarum\Event\PostWasDeleted;
use Flarum\Event\PostWasHidden;
use Flarum\Event\PostWasPosted;
use Flarum\Event\PostWasRestored;
use Flarum\Event\PostWasRevised;
use Flarum\Mentions\Notification\UserMentionedBlueprint;
use Illuminate\Contracts\Events\Dispatcher;
use s9e\TextFormatter\Utils;
class UpdateUserMentionsMetadata
{
/**
* @var NotificationSyncer
*/
protected $notifications;
/**
* @param NotificationSyncer $notifications
*/
public function __construct(NotificationSyncer $notifications)
{
$this->notifications = $notifications;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(RegisterNotificationTypes::class, [$this, 'registerNotificationType']);
$events->listen(ConfigureNotificationTypes::class, [$this, 'addNotificationType']);
$events->listen(PostWasPosted::class, [$this, 'whenPostWasPosted']);
$events->listen(PostWasRevised::class, [$this, 'whenPostWasRevised']);
$events->listen(PostWasHidden::class, [$this, 'whenPostWasHidden']);
@ -43,40 +51,61 @@ class UpdateUserMentionsMetadata
$events->listen(PostWasDeleted::class, [$this, 'whenPostWasDeleted']);
}
public function registerNotificationType(RegisterNotificationTypes $event)
/**
* @param ConfigureNotificationTypes $event
*/
public function addNotificationType(ConfigureNotificationTypes $event)
{
$event->register(
$event->add(
UserMentionedBlueprint::class,
'Flarum\Api\Serializers\PostBasicSerializer',
['alert']
);
}
/**
* @param PostWasPosted $event
*/
public function whenPostWasPosted(PostWasPosted $event)
{
$this->postBecameVisible($event->post);
}
/**
* @param PostWasRevised $event
*/
public function whenPostWasRevised(PostWasRevised $event)
{
$this->postBecameVisible($event->post);
}
/**
* @param PostWasHidden $event
*/
public function whenPostWasHidden(PostWasHidden $event)
{
$this->postBecameInvisible($event->post);
}
/**
* @param PostWasRestored $event
*/
public function whenPostWasRestored(PostWasRestored $event)
{
$this->postBecameVisible($event->post);
}
/**
* @param PostWasDeleted $event
*/
public function whenPostWasDeleted(PostWasDeleted $event)
{
$this->postBecameInvisible($event->post);
}
/**
* @param Post $post
*/
protected function postBecameVisible(Post $post)
{
$mentioned = Utils::getAttributeValues($post->parsedContent, 'USERMENTION', 'id');
@ -84,11 +113,18 @@ class UpdateUserMentionsMetadata
$this->sync($post, $mentioned);
}
/**
* @param Post $post
*/
protected function postBecameInvisible(Post $post)
{
$this->sync($post, []);
}
/**
* @param Post $post
* @param array $mentioned
*/
protected function sync(Post $post, array $mentioned)
{
$post->mentionsUsers()->sync($mentioned);

View File

@ -1,65 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Mentions\Listeners;
use Flarum\Events\ApiRelationship;
use Flarum\Events\BuildApiAction;
use Illuminate\Contracts\Events\Dispatcher;
use Flarum\Api\Serializers\PostBasicSerializer;
use Flarum\Api\Actions\Discussions;
use Flarum\Api\Actions\Posts;
class AddApiRelationships
{
public function subscribe(Dispatcher $events)
{
$events->listen(ApiRelationship::class, [$this, 'addRelationships']);
$events->listen(BuildApiAction::class, [$this, 'includeRelationships']);
}
public function addRelationships(ApiRelationship $event)
{
if ($event->serializer instanceof PostBasicSerializer) {
if ($event->relationship === 'mentionedBy') {
return $event->serializer->hasMany('Flarum\Api\Serializers\PostBasicSerializer', 'mentionedBy');
}
if ($event->relationship === 'mentionsPosts') {
return $event->serializer->hasMany('Flarum\Api\Serializers\PostBasicSerializer', 'mentionsPosts');
}
if ($event->relationship === 'mentionsUsers') {
return $event->serializer->hasMany('Flarum\Api\Serializers\PostBasicSerializer', 'mentionsUsers');
}
}
}
public function includeRelationships(BuildApiAction $event)
{
if ($event->action instanceof Discussions\ShowAction) {
$event->addInclude('posts.mentionedBy');
$event->addInclude('posts.mentionedBy.user');
$event->addInclude('posts.mentionedBy.discussion');
}
if ($event->action instanceof Posts\ShowAction ||
$event->action instanceof Posts\IndexAction) {
$event->addInclude('mentionedBy');
$event->addInclude('mentionedBy.user');
$event->addInclude('mentionedBy.discussion');
}
if ($event->action instanceof Posts\CreateAction) {
$event->addInclude('mentionsPosts');
$event->addInclude('mentionsPosts.mentionedBy');
}
}
}

View File

@ -1,51 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Mentions\Listeners;
use Flarum\Events\RegisterLocales;
use Flarum\Events\BuildClientView;
use Illuminate\Contracts\Events\Dispatcher;
class AddClientAssets
{
public function subscribe(Dispatcher $events)
{
$events->listen(RegisterLocales::class, [$this, 'addLocale']);
$events->listen(BuildClientView::class, [$this, 'addAssets']);
}
public function addLocale(RegisterLocales $event)
{
$event->addTranslations('en', __DIR__.'/../../locale/en.yml');
}
public function addAssets(BuildClientView $event)
{
$event->forumAssets([
__DIR__.'/../../js/forum/dist/extension.js',
__DIR__.'/../../less/forum/extension.less'
]);
$event->forumBootstrapper('mentions/main');
$event->forumTranslations([
'mentions.reply_to_post',
'mentions.post_mentioned_notification',
'mentions.others',
'mentions.user_mentioned_notification',
'mentions.post_mentioned_by',
'mentions.you',
'mentions.reply_link',
'mentions.notify_post_mentioned',
'mentions.notify_user_mentioned'
]);
}
}

View File

@ -1,40 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Mentions\Listeners;
use Flarum\Events\ModelRelationship;
use Flarum\Core\Posts\Post;
use Flarum\Core\Users\User;
class AddModelRelationships
{
public function subscribe($events)
{
$events->listen(ModelRelationship::class, [$this, 'addRelationships']);
}
public function addRelationships(ModelRelationship $event)
{
if ($event->model instanceof Post) {
if ($event->relationship === 'mentionedBy') {
return $event->model->belongsToMany(Post::class, 'mentions_posts', 'mentions_id', 'post_id', 'mentionedBy');
}
if ($event->relationship === 'mentionsPosts') {
return $event->model->belongsToMany(Post::class, 'mentions_posts', 'post_id', 'mentions_id', 'mentionsPosts');
}
if ($event->relationship === 'mentionsUsers') {
return $event->model->belongsToMany(User::class, 'mentions_users', 'post_id', 'mentions_id', 'mentionsUsers');
}
}
}
}

View File

@ -8,54 +8,85 @@
* file that was distributed with this source code.
*/
namespace Flarum\Mentions\Notifications;
namespace Flarum\Mentions\Notification;
use Flarum\Core\Posts\Post;
use Flarum\Core\Notifications\Blueprint;
use Flarum\Core\Notifications\MailableBlueprint;
use Flarum\Core\Post;
use Flarum\Core\Notification\BlueprintInterface;
use Flarum\Core\Notification\MailableInterface;
class PostMentionedBlueprint implements Blueprint, MailableBlueprint
class PostMentionedBlueprint implements BlueprintInterface, MailableInterface
{
/**
* @var Post
*/
public $post;
/**
* @var Post
*/
public $reply;
/**
* @param Post $post
* @param Post $reply
*/
public function __construct(Post $post, Post $reply)
{
$this->post = $post;
$this->reply = $reply;
}
/**
* {@inheritdoc}
*/
public function getSubject()
{
return $this->post;
}
/**
* {@inheritdoc}
*/
public function getSender()
{
return $this->reply->user;
}
/**
* {@inheritdoc}
*/
public function getData()
{
return ['replyNumber' => (int) $this->reply->number];
}
/**
* {@inheritdoc}
*/
public function getEmailView()
{
return ['text' => 'mentions::emails.postMentioned'];
return ['text' => 'flarum-mentions::emails.postMentioned'];
}
/**
* {@inheritdoc}
*/
public function getEmailSubject()
{
return "{$this->reply->user->username} replied to your post in {$this->post->discussion->title}";
}
/**
* {@inheritdoc}
*/
public static function getType()
{
return 'postMentioned';
}
/**
* {@inheritdoc}
*/
public static function getSubjectModel()
{
return Post::class;

View File

@ -8,52 +8,78 @@
* file that was distributed with this source code.
*/
namespace Flarum\Mentions\Notifications;
namespace Flarum\Mentions\Notification;
use Flarum\Core\Users\User;
use Flarum\Core\Posts\Post;
use Flarum\Core\Notifications\Blueprint;
use Flarum\Core\Notifications\MailableBlueprint;
use Flarum\Core\Post;
use Flarum\Core\Notification\BlueprintInterface;
use Flarum\Core\Notification\MailableInterface;
class UserMentionedBlueprint implements Blueprint, MailableBlueprint
class UserMentionedBlueprint implements BlueprintInterface, MailableInterface
{
/**
* @var Post
*/
public $post;
/**
* @param Post $post
*/
public function __construct(Post $post)
{
$this->post = $post;
}
/**
* {@inheritdoc}
*/
public function getSubject()
{
return $this->post;
}
/**
* {@inheritdoc}
*/
public function getSender()
{
return $this->post->user;
}
/**
* {@inheritdoc}
*/
public function getData()
{
return null;
}
/**
* {@inheritdoc}
*/
public function getEmailView()
{
return ['text' => 'mentions::emails.userMentioned'];
return ['text' => 'flarum-mentions::emails.userMentioned'];
}
/**
* {@inheritdoc}
*/
public function getEmailSubject()
{
return "{$this->post->user->username} mentioned you in {$this->post->discussion->title}";
}
/**
* {@inheritdoc}
*/
public static function getType()
{
return 'userMentioned';
}
/**
* {@inheritdoc}
*/
public static function getSubjectModel()
{
return Post::class;