mirror of
https://github.com/flarum/framework.git
synced 2024-12-02 06:53:47 +08:00
Rename extension to Tags. Allow multiple tags per discussion.
WIP!
This commit is contained in:
parent
f569d00314
commit
c9a03d9d8a
|
@ -6,4 +6,4 @@ require __DIR__.'/vendor/autoload.php';
|
|||
|
||||
// Register our service provider with the Flarum application. In here we can
|
||||
// register bindings and execute code when the application boots.
|
||||
return $this->app->register('Flarum\Categories\CategoriesServiceProvider');
|
||||
return $this->app->register('Flarum\Tags\TagsServiceProvider');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Flarum\\Categories\\": "src/"
|
||||
"Flarum\\Tags\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,16 @@
|
|||
{
|
||||
"name": "flarum-categories",
|
||||
"title": "Categories",
|
||||
"description": "Organise discussions into a heirarchy of categories.",
|
||||
"tags": [
|
||||
"discussions"
|
||||
],
|
||||
"name": "flarum-tags",
|
||||
"title": "Tags",
|
||||
"description": "Organise discussions into a heirarchy of tags and categories.",
|
||||
"tags": [],
|
||||
"version": "0.1.0",
|
||||
"author": {
|
||||
"name": "Toby Zerner",
|
||||
"email": "toby@flarum.org",
|
||||
"homepage": "http://tobyzerner.com"
|
||||
"email": "toby.zerner@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": ">=5.4.0",
|
||||
"flarum": ">0.1.0"
|
||||
},
|
||||
"links": {
|
||||
"github": "https://github.com/flarum/categories",
|
||||
"issues": "https://github.com/flarum/categories/issues"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
var gulp = require('flarum-gulp');
|
||||
|
||||
gulp({
|
||||
modulePrefix: 'flarum-categories'
|
||||
modulePrefix: 'flarum-tags'
|
||||
});
|
||||
|
|
241
extensions/tags/js/bootstrap.js
vendored
241
extensions/tags/js/bootstrap.js
vendored
|
@ -1,237 +1,34 @@
|
|||
import { extend, override } from 'flarum/extension-utils';
|
||||
import app from 'flarum/app';
|
||||
import Model from 'flarum/model';
|
||||
import Discussion from 'flarum/models/discussion';
|
||||
import IndexPage from 'flarum/components/index-page';
|
||||
import DiscussionPage from 'flarum/components/discussion-page';
|
||||
import DiscussionList from 'flarum/components/discussion-list';
|
||||
import DiscussionHero from 'flarum/components/discussion-hero';
|
||||
import Separator from 'flarum/components/separator';
|
||||
import ActionButton from 'flarum/components/action-button';
|
||||
import NavItem from 'flarum/components/nav-item';
|
||||
import DiscussionComposer from 'flarum/components/discussion-composer';
|
||||
import SettingsPage from 'flarum/components/settings-page';
|
||||
import PostedActivity from 'flarum/components/posted-activity';
|
||||
import icon from 'flarum/helpers/icon';
|
||||
import app from 'flarum/app';
|
||||
|
||||
import Category from 'flarum-categories/models/category';
|
||||
import CategoriesPage from 'flarum-categories/components/categories-page';
|
||||
import CategoryHero from 'flarum-categories/components/category-hero';
|
||||
import CategoryNavItem from 'flarum-categories/components/category-nav-item';
|
||||
import MoveDiscussionModal from 'flarum-categories/components/move-discussion-modal';
|
||||
import DiscussionMovedNotification from 'flarum-categories/components/discussion-moved-notification';
|
||||
import DiscussionMovedPost from 'flarum-categories/components/discussion-moved-post';
|
||||
import categoryLabel from 'flarum-categories/helpers/category-label';
|
||||
import categoryIcon from 'flarum-categories/helpers/category-icon';
|
||||
import Tag from 'flarum-tags/models/tag';
|
||||
import TagsPage from 'flarum-tags/components/tags-page';
|
||||
import addTagList from 'flarum-tags/add-tag-list';
|
||||
import addTagFilter from 'flarum-tags/add-tag-filter';
|
||||
import addTagLabels from 'flarum-tags/add-tag-labels';
|
||||
|
||||
app.initializers.add('flarum-categories', function() {
|
||||
app.initializers.add('flarum-tags', function() {
|
||||
// Register routes.
|
||||
app.routes['categories'] = ['/categories', CategoriesPage.component()];
|
||||
app.routes['category'] = ['/c/:categories', IndexPage.component()];
|
||||
|
||||
// @todo support combination with filters
|
||||
// app.routes['category.filter'] = ['/c/:slug/:filter', IndexPage.component({category: true})];
|
||||
app.routes['tags'] = ['/tags', TagsPage.component()];
|
||||
app.routes['tag'] = ['/t/:tags', IndexPage.component()];
|
||||
|
||||
// Register models.
|
||||
app.store.models['categories'] = Category;
|
||||
Discussion.prototype.category = Model.one('category');
|
||||
app.store.models['tags'] = Tag;
|
||||
Discussion.prototype.tags = Model.many('tags');
|
||||
Discussion.prototype.canMove = Model.prop('canMove');
|
||||
|
||||
// Register components.
|
||||
app.postComponentRegistry['discussionMoved'] = DiscussionMovedPost;
|
||||
app.notificationComponentRegistry['discussionMoved'] = DiscussionMovedNotification;
|
||||
// Add a list of tags to the index navigation.
|
||||
addTagList();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// INDEX PAGE
|
||||
// ---------------------------------------------------------------------------
|
||||
// When a tag is selected, filter the discussion list by that tag.
|
||||
addTagFilter();
|
||||
|
||||
// Add a category label to each discussion in the discussion list.
|
||||
extend(DiscussionList.prototype, 'infoItems', function(items, discussion) {
|
||||
var category = discussion.category();
|
||||
if (category && category.slug() !== this.props.params.categories) {
|
||||
items.add('category', categoryLabel(category), {first: true});
|
||||
}
|
||||
});
|
||||
// Add tags to the discussion list and discussion hero.
|
||||
addTagLabels();
|
||||
|
||||
// Add a link to the categories page, as well as a list of all the categories,
|
||||
// to the index page's sidebar.
|
||||
extend(IndexPage.prototype, 'navItems', function(items) {
|
||||
items.add('categories', NavItem.component({
|
||||
icon: 'reorder',
|
||||
label: 'Categories',
|
||||
href: app.route('categories'),
|
||||
config: m.route
|
||||
}), {last: true});
|
||||
// addMoveDiscussionControl();
|
||||
|
||||
items.add('separator', Separator.component(), {last: true});
|
||||
|
||||
items.add('uncategorized', CategoryNavItem.component({params: this.stickyParams()}), {last: true});
|
||||
|
||||
app.store.all('categories').sort((a, b) => a.position() - b.position()).forEach(category => {
|
||||
items.add('category'+category.id(), CategoryNavItem.component({category, params: this.stickyParams()}), {last: true});
|
||||
});
|
||||
});
|
||||
|
||||
IndexPage.prototype.currentCategory = function() {
|
||||
var slug = this.params().categories;
|
||||
if (slug) {
|
||||
return app.store.getBy('categories', 'slug', slug);
|
||||
}
|
||||
};
|
||||
|
||||
// If currently viewing a category, insert a category hero at the top of the
|
||||
// view.
|
||||
extend(IndexPage.prototype, 'view', function(view) {
|
||||
var category = this.currentCategory();
|
||||
if (category) {
|
||||
view.children[0] = CategoryHero.component({category});
|
||||
}
|
||||
});
|
||||
|
||||
// If currently viewing a category, restyle the 'new discussion' button to use
|
||||
// the category's color.
|
||||
extend(IndexPage.prototype, 'sidebarItems', function(items) {
|
||||
var category = this.currentCategory();
|
||||
if (category) {
|
||||
items.newDiscussion.content.props.style = 'background-color: '+category.color();
|
||||
}
|
||||
});
|
||||
|
||||
// Add a parameter for the IndexPage to pass on to the DiscussionList that
|
||||
// will let us filter discussions by category.
|
||||
extend(IndexPage.prototype, 'params', function(params) {
|
||||
params.categories = m.route.param('categories');
|
||||
});
|
||||
|
||||
// Translate that parameter into a gambit appended to the search query.
|
||||
extend(DiscussionList.prototype, 'params', function(params) {
|
||||
params.include.push('category');
|
||||
if (params.categories) {
|
||||
params.q = (params.q || '')+' category:'+params.categories;
|
||||
delete params.categories;
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DISCUSSION PAGE
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Include a discussion's category when fetching it.
|
||||
extend(DiscussionPage.prototype, 'params', function(params) {
|
||||
params.include.push('category');
|
||||
});
|
||||
|
||||
// Restyle a discussion's hero to use its category color.
|
||||
extend(DiscussionHero.prototype, 'view', function(view) {
|
||||
var category = this.props.discussion.category();
|
||||
if (category) {
|
||||
view.attrs.style = 'color: #fff; background-color: '+category.color();
|
||||
}
|
||||
});
|
||||
|
||||
// Add the name of a discussion's category to the discussion hero, displayed
|
||||
// before the title. Put the title on its own line.
|
||||
extend(DiscussionHero.prototype, 'items', function(items) {
|
||||
var category = this.props.discussion.category();
|
||||
if (category) {
|
||||
items.add('category', m('a', {
|
||||
href: app.route('category', {categories: category.slug()}),
|
||||
config: m.route
|
||||
}, categoryLabel(category)), {before: 'title'});
|
||||
|
||||
items.title.content.wrapperClass = 'block-item';
|
||||
}
|
||||
});
|
||||
|
||||
// Add a control allowing the discussion to be moved to another category.
|
||||
extend(Discussion.prototype, 'controls', function(items) {
|
||||
if (this.canMove()) {
|
||||
items.add('move', ActionButton.component({
|
||||
label: 'Move',
|
||||
icon: 'arrow-right',
|
||||
onclick: () => app.modal.show(new MoveDiscussionModal({discussion: this}))
|
||||
}), {after: 'rename'});
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// COMPOSER
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// When the 'new discussion' button is clicked...
|
||||
override(IndexPage.prototype, 'newDiscussion', function(original) {
|
||||
var slug = this.params().categories;
|
||||
|
||||
// If we're currently viewing a specific category, or if the user isn't
|
||||
// logged in, then we'll let the core code proceed. If that results in the
|
||||
// composer appearing, we'll set the composer's current category to the one
|
||||
// we're viewing.
|
||||
if (slug || !app.session.user()) {
|
||||
if (original()) {
|
||||
var category = app.store.getBy('categories', 'slug', slug);
|
||||
app.composer.component.category(category);
|
||||
}
|
||||
} else {
|
||||
// If we're logged in and we're viewing All Discussions, we'll present the
|
||||
// user with a category selection dialog before proceeding to show the
|
||||
// composer.
|
||||
var modal = new MoveDiscussionModal({
|
||||
onchange: category => {
|
||||
original();
|
||||
app.composer.component.category(category);
|
||||
}
|
||||
});
|
||||
app.modal.show(modal);
|
||||
}
|
||||
});
|
||||
|
||||
// Add category-selection abilities to the discussion composer.
|
||||
DiscussionComposer.prototype.category = m.prop();
|
||||
DiscussionComposer.prototype.chooseCategory = function() {
|
||||
var modal = new MoveDiscussionModal({
|
||||
onchange: category => {
|
||||
this.category(category);
|
||||
this.$('textarea').focus();
|
||||
}
|
||||
});
|
||||
app.modal.show(modal);
|
||||
};
|
||||
|
||||
// Add a category-selection menu to the discussion composer's header, after
|
||||
// the title.
|
||||
extend(DiscussionComposer.prototype, 'headerItems', function(items) {
|
||||
var category = this.category();
|
||||
|
||||
items.add('category', m('a[href=javascript:;][tabindex=-1].btn.btn-link.control-change-category', {onclick: this.chooseCategory.bind(this)}, [
|
||||
categoryIcon(category), ' ',
|
||||
m('span.label', category ? category.title() : 'Uncategorized'),
|
||||
icon('sort')
|
||||
]));
|
||||
});
|
||||
|
||||
// Add the selected category as data to submit to the server.
|
||||
extend(DiscussionComposer.prototype, 'data', function(data) {
|
||||
data.links = data.links || {};
|
||||
data.links.category = this.category();
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// USER PROFILE
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Add a category label next to the discussion title in post activity items.
|
||||
extend(PostedActivity.prototype, 'headerItems', function(items) {
|
||||
var category = this.props.activity.subject().discussion().category();
|
||||
if (category) {
|
||||
items.add('category', categoryLabel(category));
|
||||
}
|
||||
});
|
||||
|
||||
// Add a notification preference.
|
||||
extend(SettingsPage.prototype, 'notificationTypes', function(items) {
|
||||
items.add('discussionMoved', {
|
||||
name: 'discussionMoved',
|
||||
label: [icon('arrow-right'), ' Someone moves a discussion I started']
|
||||
});
|
||||
});
|
||||
// addDiscussionComposer();
|
||||
});
|
||||
|
|
50
extensions/tags/js/src/add-tag-filter.js
Normal file
50
extensions/tags/js/src/add-tag-filter.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { extend } from 'flarum/extension-utils';
|
||||
import IndexPage from 'flarum/components/index-page';
|
||||
import DiscussionList from 'flarum/components/discussion-list';
|
||||
|
||||
import TagHero from 'flarum-tags/components/tag-hero';
|
||||
|
||||
export default function() {
|
||||
IndexPage.prototype.currentTag = function() {
|
||||
var slug = this.params().tags;
|
||||
if (slug) {
|
||||
return app.store.getBy('tags', 'slug', slug);
|
||||
}
|
||||
};
|
||||
|
||||
// If currently viewing a tag, insert a tag hero at the top of the
|
||||
// view.
|
||||
extend(IndexPage.prototype, 'view', function(view) {
|
||||
var tag = this.currentTag();
|
||||
if (tag) {
|
||||
view.children[0] = TagHero.component({tag});
|
||||
}
|
||||
});
|
||||
|
||||
// If currently viewing a tag, restyle the 'new discussion' button to use
|
||||
// the tag's color.
|
||||
extend(IndexPage.prototype, 'sidebarItems', function(items) {
|
||||
var tag = this.currentTag();
|
||||
if (tag) {
|
||||
var color = tag.color();
|
||||
if (color) {
|
||||
items.newDiscussion.content.props.style = 'background-color: '+color;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add a parameter for the IndexPage to pass on to the DiscussionList that
|
||||
// will let us filter discussions by tag.
|
||||
extend(IndexPage.prototype, 'params', function(params) {
|
||||
params.tags = m.route.param('tags');
|
||||
});
|
||||
|
||||
// Translate that parameter into a gambit appended to the search query.
|
||||
extend(DiscussionList.prototype, 'params', function(params) {
|
||||
params.include.push('tags');
|
||||
if (params.tags) {
|
||||
params.q = (params.q || '')+' tag:'+params.tags;
|
||||
delete params.tags;
|
||||
}
|
||||
});
|
||||
};
|
40
extensions/tags/js/src/add-tag-labels.js
Normal file
40
extensions/tags/js/src/add-tag-labels.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { extend } from 'flarum/extension-utils';
|
||||
import DiscussionList from 'flarum/components/discussion-list';
|
||||
import DiscussionPage from 'flarum/components/discussion-page';
|
||||
import DiscussionHero from 'flarum/components/discussion-hero';
|
||||
|
||||
import tagsLabel from 'flarum-tags/helpers/tags-label';
|
||||
|
||||
export default function() {
|
||||
// Add tag labels to each discussion in the discussion list.
|
||||
extend(DiscussionList.prototype, 'infoItems', function(items, discussion) {
|
||||
var tags = discussion.tags();
|
||||
if (tags) {
|
||||
items.add('tags', tagsLabel(tags.filter(tag => tag.slug() !== this.props.params.tags)), {first: true});
|
||||
}
|
||||
});
|
||||
|
||||
// Include a discussion's tags when fetching it.
|
||||
extend(DiscussionPage.prototype, 'params', function(params) {
|
||||
params.include.push('tags');
|
||||
});
|
||||
|
||||
// Restyle a discussion's hero to use its first tag's color.
|
||||
extend(DiscussionHero.prototype, 'view', function(view) {
|
||||
var tags = this.props.discussion.tags();
|
||||
if (tags) {
|
||||
view.attrs.style = 'color: #fff; background-color: '+tags[0].color();
|
||||
}
|
||||
});
|
||||
|
||||
// Add a list of a discussion's tags to the discussion hero, displayed
|
||||
// before the title. Put the title on its own line.
|
||||
extend(DiscussionHero.prototype, 'items', function(items) {
|
||||
var tags = this.props.discussion.tags();
|
||||
if (tags) {
|
||||
items.add('tags', tagsLabel(tags, {link: true}), {before: 'title'});
|
||||
|
||||
items.title.content.wrapperClass = 'block-item';
|
||||
}
|
||||
});
|
||||
};
|
50
extensions/tags/js/src/add-tag-list.js
Normal file
50
extensions/tags/js/src/add-tag-list.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { extend } from 'flarum/extension-utils';
|
||||
import IndexPage from 'flarum/components/index-page';
|
||||
import NavItem from 'flarum/components/nav-item';
|
||||
import Separator from 'flarum/components/separator';
|
||||
|
||||
import TagNavItem from 'flarum-tags/components/tag-nav-item';
|
||||
|
||||
export default function() {
|
||||
// Add a link to the tags page, as well as a list of all the tags,
|
||||
// to the index page's sidebar.
|
||||
extend(IndexPage.prototype, 'navItems', function(items) {
|
||||
items.add('tags', NavItem.component({
|
||||
icon: 'reorder',
|
||||
label: 'Tags',
|
||||
href: app.route('tags'),
|
||||
config: m.route
|
||||
}), {last: true});
|
||||
|
||||
items.add('separator', Separator.component(), {last: true});
|
||||
|
||||
var params = this.stickyParams();
|
||||
var tags = app.store.all('tags');
|
||||
|
||||
items.add('untagged', TagNavItem.component({params}), {last: true});
|
||||
|
||||
var addTag = tag => {
|
||||
var currentTag = this.currentTag();
|
||||
var active = currentTag === tag;
|
||||
if (!active && currentTag) {
|
||||
currentTag = currentTag.parent();
|
||||
active = currentTag === tag;
|
||||
}
|
||||
items.add('tag'+tag.id(), TagNavItem.component({tag, params, active}), {last: true});
|
||||
}
|
||||
|
||||
tags.filter(tag => tag.position() !== null && !tag.isChild()).sort((a, b) => a.position() - b.position()).forEach(addTag);
|
||||
|
||||
var more = tags.filter(tag => tag.position() === null).sort((a, b) => b.discussionsCount() - a.discussionsCount());
|
||||
|
||||
more.splice(0, 3).forEach(addTag);
|
||||
|
||||
if (more.length) {
|
||||
items.add('moreTags', NavItem.component({
|
||||
label: 'More...',
|
||||
href: app.route('tags'),
|
||||
config: m.route
|
||||
}), {last: true});;
|
||||
}
|
||||
});
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
import Component from 'flarum/component';
|
||||
|
||||
export default class CategoryHero extends Component {
|
||||
view() {
|
||||
var category = this.props.category;
|
||||
|
||||
return m('header.hero.category-hero', {style: 'color: #fff; background-color: '+category.color()}, [
|
||||
m('div.container', [
|
||||
m('div.container-narrow', [
|
||||
m('h2', category.title()),
|
||||
m('div.subtitle', category.description())
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import NavItem from 'flarum/components/nav-item';
|
||||
import categoryIcon from 'flarum-categories/helpers/category-icon';
|
||||
|
||||
export default class CategoryNavItem extends NavItem {
|
||||
view() {
|
||||
var category = this.props.category;
|
||||
var active = this.constructor.active(this.props);
|
||||
return m('li'+(active ? '.active' : ''), m('a', {href: this.props.href, config: m.route, onclick: () => {app.cache.discussionList = null; m.redraw.strategy('none')}, style: (active && category) ? 'color: '+category.color() : '', title: category ? category.description() : ''}, [
|
||||
categoryIcon(category, {className: 'icon'}),
|
||||
this.props.label
|
||||
]));
|
||||
}
|
||||
|
||||
static props(props) {
|
||||
var category = props.category;
|
||||
props.params.categories = category ? category.slug() : 'uncategorized';
|
||||
props.href = app.route('category', props.params);
|
||||
props.label = category ? category.title() : 'Uncategorized';
|
||||
}
|
||||
}
|
17
extensions/tags/js/src/components/tag-hero.js
Normal file
17
extensions/tags/js/src/components/tag-hero.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import Component from 'flarum/component';
|
||||
|
||||
export default class TagHero extends Component {
|
||||
view() {
|
||||
var tag = this.props.tag;
|
||||
var color = tag.color();
|
||||
|
||||
return m('header.hero.tag-hero', {style: color ? 'color: #fff; background-color: '+tag.color() : ''}, [
|
||||
m('div.container', [
|
||||
m('div.container-narrow', [
|
||||
m('h2', tag.name()),
|
||||
m('div.subtitle', tag.description())
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
39
extensions/tags/js/src/components/tag-nav-item.js
Normal file
39
extensions/tags/js/src/components/tag-nav-item.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import NavItem from 'flarum/components/nav-item';
|
||||
import tagIcon from 'flarum-tags/helpers/tag-icon';
|
||||
|
||||
export default class TagNavItem extends NavItem {
|
||||
view() {
|
||||
var tag = this.props.tag;
|
||||
var active = this.constructor.active(this.props);
|
||||
var description = tag && tag.description();
|
||||
var children;
|
||||
|
||||
if (active && tag) {
|
||||
children = app.store.all('tags').filter(child => {
|
||||
var parent = child.parent();
|
||||
return parent && parent.id() == tag.id();
|
||||
});
|
||||
}
|
||||
|
||||
return m('li'+(active ? '.active' : ''),
|
||||
m('a', {
|
||||
href: this.props.href,
|
||||
config: m.route,
|
||||
onclick: () => {app.cache.discussionList = null; m.redraw.strategy('none')},
|
||||
style: (active && tag) ? 'color: '+tag.color() : '',
|
||||
title: description || ''
|
||||
}, [
|
||||
tagIcon(tag, {className: 'icon'}),
|
||||
this.props.label
|
||||
]),
|
||||
children && children.length ? m('ul.dropdown-menu', children.map(tag => TagNavItem.component({tag, params: this.props.params}))) : ''
|
||||
);
|
||||
}
|
||||
|
||||
static props(props) {
|
||||
var tag = props.tag;
|
||||
props.params.tags = tag ? tag.slug() : 'untagged';
|
||||
props.href = app.route('tag', props.params);
|
||||
props.label = tag ? tag.name() : 'Untagged';
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import Component from 'flarum/component';
|
|||
import WelcomeHero from 'flarum/components/welcome-hero';
|
||||
import icon from 'flarum/helpers/icon';
|
||||
|
||||
export default class CategoriesPage extends Component {
|
||||
export default class TagsPage extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
export default function categoryIcon(category, attrs) {
|
||||
attrs = attrs || {};
|
||||
|
||||
if (category) {
|
||||
attrs.style = attrs.style || {};
|
||||
attrs.style.backgroundColor = category.color();
|
||||
} else {
|
||||
attrs.className = (attrs.className || '')+' uncategorized';
|
||||
}
|
||||
|
||||
return m('span.icon.category-icon', attrs);
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
export default function categoryLabel(category, attrs) {
|
||||
attrs = attrs || {};
|
||||
|
||||
if (category) {
|
||||
attrs.style = attrs.style || {};
|
||||
attrs.style.backgroundColor = attrs.style.color = category.color();
|
||||
} else {
|
||||
attrs.className = (attrs.className || '')+' uncategorized';
|
||||
}
|
||||
|
||||
return m('span.category-label', attrs, m('span.category-label-text', category ? category.title() : 'Uncategorized'));
|
||||
}
|
12
extensions/tags/js/src/helpers/tag-icon.js
Normal file
12
extensions/tags/js/src/helpers/tag-icon.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
export default function tagIcon(tag, attrs) {
|
||||
attrs = attrs || {};
|
||||
|
||||
if (tag) {
|
||||
attrs.style = attrs.style || {};
|
||||
attrs.style.backgroundColor = tag.color();
|
||||
} else {
|
||||
attrs.className = (attrs.className || '')+' untagged';
|
||||
}
|
||||
|
||||
return m('span.icon.tag-icon', attrs);
|
||||
}
|
24
extensions/tags/js/src/helpers/tag-label.js
Normal file
24
extensions/tags/js/src/helpers/tag-label.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
export default function tagsLabel(tag, attrs) {
|
||||
attrs = attrs || {};
|
||||
attrs.style = attrs.style || {};
|
||||
attrs.className = attrs.className || '';
|
||||
|
||||
var link = attrs.link;
|
||||
delete attrs.link;
|
||||
if (link) {
|
||||
attrs.href = app.route('tag', {tags: tag.slug()});
|
||||
attrs.config = m.route;
|
||||
}
|
||||
|
||||
if (tag) {
|
||||
var color = tag.color();
|
||||
if (color) {
|
||||
attrs.style.backgroundColor = attrs.style.color = color;
|
||||
attrs.className += ' colored';
|
||||
}
|
||||
} else {
|
||||
attrs.className += ' untagged';
|
||||
}
|
||||
|
||||
return m((link ? 'a' : 'span')+'.tag-label', attrs, m('span.tag-label-text', tag ? tag.name() : 'Untagged'));
|
||||
}
|
19
extensions/tags/js/src/helpers/tags-label.js
Normal file
19
extensions/tags/js/src/helpers/tags-label.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import tagLabel from 'flarum-tags/helpers/tag-label';
|
||||
|
||||
export default function tagsLabel(tags, attrs) {
|
||||
attrs = attrs || {};
|
||||
var children = [];
|
||||
|
||||
var link = attrs.link;
|
||||
delete attrs.link;
|
||||
|
||||
if (tags) {
|
||||
tags.forEach(tag => {
|
||||
children.push(tagLabel(tag, {link}));
|
||||
});
|
||||
} else {
|
||||
children.push(tagLabel());
|
||||
}
|
||||
|
||||
return m('span.tags-label', attrs, children);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import Model from 'flarum/model';
|
||||
|
||||
class Category extends Model {}
|
||||
|
||||
Category.prototype.id = Model.prop('id');
|
||||
Category.prototype.title = Model.prop('title');
|
||||
Category.prototype.slug = Model.prop('slug');
|
||||
Category.prototype.description = Model.prop('description');
|
||||
Category.prototype.color = Model.prop('color');
|
||||
Category.prototype.discussionsCount = Model.prop('discussionsCount');
|
||||
Category.prototype.position = Model.prop('position');
|
||||
|
||||
export default Category;
|
18
extensions/tags/js/src/models/tag.js
Normal file
18
extensions/tags/js/src/models/tag.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import Model from 'flarum/model';
|
||||
|
||||
class Tag extends Model {}
|
||||
|
||||
Tag.prototype.id = Model.prop('id');
|
||||
Tag.prototype.name = Model.prop('name');
|
||||
Tag.prototype.slug = Model.prop('slug');
|
||||
Tag.prototype.description = Model.prop('description');
|
||||
Tag.prototype.color = Model.prop('color');
|
||||
Tag.prototype.backgroundUrl = Model.prop('backgroundUrl');
|
||||
Tag.prototype.iconUrl = Model.prop('iconUrl');
|
||||
Tag.prototype.discussionsCount = Model.prop('discussionsCount');
|
||||
Tag.prototype.position = Model.prop('position');
|
||||
Tag.prototype.parent = Model.one('parent');
|
||||
Tag.prototype.defaultSort = Model.prop('defaultSort');
|
||||
Tag.prototype.isChild = Model.prop('isChild');
|
||||
|
||||
export default Tag;
|
93
extensions/tags/less/extension.less
Normal file
93
extensions/tags/less/extension.less
Normal file
|
@ -0,0 +1,93 @@
|
|||
.tag-label {
|
||||
font-size: 85%;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
padding: 0.2em 0.55em;
|
||||
border-radius: @border-radius-base;
|
||||
background: @fl-body-secondary-color;
|
||||
|
||||
&.untagged {
|
||||
background: transparent;
|
||||
border: 1px dotted @fl-body-muted-color;
|
||||
color: @fl-body-muted-color;
|
||||
}
|
||||
|
||||
&.colored {
|
||||
& .tag-label-text {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-hero .tags-label & {
|
||||
background: transparent;
|
||||
border-radius: 4px !important;
|
||||
|
||||
&.colored {
|
||||
margin-right: 5px;
|
||||
background: #fff !important;
|
||||
color: @fl-body-muted-color;
|
||||
|
||||
& .tag-label-text {
|
||||
color: inherit !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-moved-post & {
|
||||
margin: 0 2px;
|
||||
}
|
||||
}
|
||||
.tags-label {
|
||||
.discussion-summary & {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
& .tag-label {
|
||||
border-radius: 0;
|
||||
margin-right: 1px;
|
||||
|
||||
&:first-child {
|
||||
border-radius: @border-radius-base 0 0 @border-radius-base;
|
||||
}
|
||||
&:last-child {
|
||||
border-radius: 0 @border-radius-base @border-radius-base 0;
|
||||
}
|
||||
&:first-child:last-child {
|
||||
border-radius: @border-radius-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @todo give all <li>s a class in core, get rid of block-item
|
||||
.discussion-hero {
|
||||
& .block-item {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-icon {
|
||||
border-radius: @border-radius-base;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
vertical-align: -3px;
|
||||
margin-left: 1px;
|
||||
background: @fl-body-secondary-color;
|
||||
|
||||
&.untagged {
|
||||
border: 1px dotted @fl-body-muted-color;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
.side-nav .dropdown-menu > li > .dropdown-menu {
|
||||
margin-bottom: 10px;
|
||||
|
||||
& .tag-icon {
|
||||
display: none;
|
||||
}
|
||||
& > li > a {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateCategoriesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('categories', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('title');
|
||||
$table->string('slug');
|
||||
$table->text('description');
|
||||
$table->string('color');
|
||||
$table->integer('discussions_count')->unsigned()->default(0);
|
||||
$table->integer('position')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('categories');
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddCategoryToDiscussions extends Migration
|
||||
class CreateDiscussionsTagsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
|
@ -12,8 +12,10 @@ class AddCategoryToDiscussions extends Migration
|
|||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('discussions', function (Blueprint $table) {
|
||||
$table->integer('category_id')->unsigned()->nullable();
|
||||
Schema::create('discussions_tags', function (Blueprint $table) {
|
||||
$table->integer('discussion_id')->unsigned();
|
||||
$table->integer('tag_id')->unsigned();
|
||||
$table->primary(['discussion_id', 'tag_id']);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -24,8 +26,6 @@ class AddCategoryToDiscussions extends Migration
|
|||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('discussions', function (Blueprint $table) {
|
||||
$table->dropColumn('category_id');
|
||||
});
|
||||
Schema::drop('discussions_tags');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateTagsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tags', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('name', 100);
|
||||
$table->string('slug', 100);
|
||||
$table->text('description')->nullable();
|
||||
$table->string('color', 50)->nullable();
|
||||
$table->string('background_path', 100)->nullable();
|
||||
$table->string('icon_path', 100)->nullable();
|
||||
$table->integer('discussions_count')->unsigned()->default(0);
|
||||
$table->integer('position')->nullable();
|
||||
$table->integer('parent_id')->unsigned()->nullable();
|
||||
$table->string('default_sort', 50)->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('tags');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateUsersTagsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('users_tags', function (Blueprint $table) {
|
||||
$table->integer('user_id')->unsigned();
|
||||
$table->integer('tag_id')->unsigned();
|
||||
$table->dateTime('read_time')->nullable();
|
||||
$table->boolean('is_hidden')->default(0);
|
||||
$table->primary(['user_id', 'tag_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('users_tags');
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
<?php namespace Flarum\Categories;
|
||||
|
||||
use Flarum\Core\Models\Model;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
protected $table = 'categories';
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
<?php namespace Flarum\Categories;
|
||||
|
||||
use Flarum\Core\Repositories\UserRepositoryInterface as UserRepository;
|
||||
use Flarum\Core\Search\SearcherInterface;
|
||||
use Flarum\Core\Search\GambitAbstract;
|
||||
|
||||
class CategoryGambit extends GambitAbstract
|
||||
{
|
||||
/**
|
||||
* The gambit's regex pattern.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pattern = 'category:(.+)';
|
||||
|
||||
/**
|
||||
* @var \Flarum\Categories\CategoryRepositoryInterface
|
||||
*/
|
||||
protected $categories;
|
||||
|
||||
/**
|
||||
* Instantiate the gambit.
|
||||
*
|
||||
* @param \Flarum\Categories\CategoryRepositoryInterface $categories
|
||||
*/
|
||||
public function __construct(CategoryRepositoryInterface $categories)
|
||||
{
|
||||
$this->categories = $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply conditions to the searcher, given matches from the gambit's
|
||||
* regex.
|
||||
*
|
||||
* @param array $matches The matches from the gambit's regex.
|
||||
* @param \Flarum\Core\Search\SearcherInterface $searcher
|
||||
* @return void
|
||||
*/
|
||||
public function conditions($matches, SearcherInterface $searcher)
|
||||
{
|
||||
$slugs = explode(',', trim($matches[1], '"'));
|
||||
|
||||
$searcher->query()->where(function ($query) use ($slugs) {
|
||||
foreach ($slugs as $slug) {
|
||||
if ($slug === 'uncategorized') {
|
||||
$query->orWhereNull('category_id');
|
||||
} else {
|
||||
$id = $this->categories->getIdForSlug($slug);
|
||||
$query->orWhere('category_id', $id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php namespace Flarum\Categories;
|
||||
|
||||
use Flarum\Api\Serializers\BaseSerializer;
|
||||
|
||||
class CategorySerializer extends BaseSerializer
|
||||
{
|
||||
/**
|
||||
* The resource type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'categories';
|
||||
|
||||
/**
|
||||
* Serialize category attributes to be exposed in the API.
|
||||
*
|
||||
* @param \Flarum\Categories\Category $category
|
||||
* @return array
|
||||
*/
|
||||
protected function attributes($category)
|
||||
{
|
||||
$attributes = [
|
||||
'title' => $category->title,
|
||||
'description' => $category->description,
|
||||
'slug' => $category->slug,
|
||||
'color' => $category->color,
|
||||
'discussionsCount' => (int) $category->discussions_count,
|
||||
'position' => (int) $category->position
|
||||
];
|
||||
|
||||
return $this->extendAttributes($category, $attributes);
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
<?php namespace Flarum\Categories;
|
||||
<?php namespace Flarum\Tags;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Flarum\Core\Models\User;
|
||||
use Flarum\Categories\Category;
|
||||
use Flarum\Tags\Tag;
|
||||
|
||||
class EloquentCategoryRepository implements CategoryRepositoryInterface
|
||||
class EloquentTagRepository implements TagRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Find all categories, optionally making sure they are visible to a
|
||||
* Find all tags, optionally making sure they are visible to a
|
||||
* certain user.
|
||||
*
|
||||
* @param \Flarum\Core\Models\User|null $user
|
||||
|
@ -15,13 +15,13 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
|
|||
*/
|
||||
public function find(User $user = null)
|
||||
{
|
||||
$query = Category::newQuery();
|
||||
$query = Tag::newQuery();
|
||||
|
||||
return $this->scopeVisibleForUser($query, $user)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of a category with the given slug.
|
||||
* Get the ID of a tag with the given slug.
|
||||
*
|
||||
* @param string $slug
|
||||
* @param \Flarum\Core\Models\User|null $user
|
||||
|
@ -29,7 +29,7 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
|
|||
*/
|
||||
public function getIdForSlug($slug, User $user = null)
|
||||
{
|
||||
$query = Category::where('slug', 'like', $slug);
|
||||
$query = Tag::where('slug', 'like', $slug);
|
||||
|
||||
return $this->scopeVisibleForUser($query, $user)->pluck('id');
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
<?php namespace Flarum\Categories\Handlers;
|
||||
<?php namespace Flarum\Tags\Handlers;
|
||||
|
||||
use Flarum\Categories\Category;
|
||||
use Flarum\Categories\CategorySerializer;
|
||||
use Flarum\Tags\Tag;
|
||||
use Flarum\Tags\TagSerializer;
|
||||
use Flarum\Forum\Events\RenderView;
|
||||
|
||||
class CategoryPreloader
|
||||
class TagPreloader
|
||||
{
|
||||
public function subscribe($events)
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ class CategoryPreloader
|
|||
|
||||
public function renderForum(RenderView $event)
|
||||
{
|
||||
$serializer = new CategorySerializer($event->action->actor);
|
||||
$event->view->data = array_merge($event->view->data, $serializer->collection(Category::orderBy('position')->get())->toArray());
|
||||
$serializer = new TagSerializer($event->action->actor, null, ['parent']);
|
||||
$event->view->data = array_merge($event->view->data, $serializer->collection(Tag::orderBy('position')->get())->toArray());
|
||||
}
|
||||
}
|
8
extensions/tags/src/Tag.php
Normal file
8
extensions/tags/src/Tag.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php namespace Flarum\Tags;
|
||||
|
||||
use Flarum\Core\Models\Model;
|
||||
|
||||
class Tag extends Model
|
||||
{
|
||||
protected $table = 'tags';
|
||||
}
|
63
extensions/tags/src/TagGambit.php
Normal file
63
extensions/tags/src/TagGambit.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php namespace Flarum\Tags;
|
||||
|
||||
use Flarum\Core\Repositories\UserRepositoryInterface as UserRepository;
|
||||
use Flarum\Core\Search\SearcherInterface;
|
||||
use Flarum\Core\Search\GambitAbstract;
|
||||
|
||||
class TagGambit extends GambitAbstract
|
||||
{
|
||||
/**
|
||||
* The gambit's regex pattern.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pattern = 'tag:(.+)';
|
||||
|
||||
/**
|
||||
* @var \Flarum\Tags\TagRepositoryInterface
|
||||
*/
|
||||
protected $tags;
|
||||
|
||||
/**
|
||||
* Instantiate the gambit.
|
||||
*
|
||||
* @param \Flarum\Tags\TagRepositoryInterface $categories
|
||||
*/
|
||||
public function __construct(TagRepositoryInterface $tags)
|
||||
{
|
||||
$this->tags = $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply conditions to the searcher, given matches from the gambit's
|
||||
* regex.
|
||||
*
|
||||
* @param array $matches The matches from the gambit's regex.
|
||||
* @param \Flarum\Core\Search\SearcherInterface $searcher
|
||||
* @return void
|
||||
*/
|
||||
public function conditions($matches, SearcherInterface $searcher)
|
||||
{
|
||||
$slugs = explode(',', trim($matches[1], '"'));
|
||||
|
||||
$searcher->query()->where(function ($query) use ($slugs) {
|
||||
foreach ($slugs as $slug) {
|
||||
if ($slug === 'uncategorized') {
|
||||
$query->orWhereNotExists(function ($query) {
|
||||
$query->select(app('db')->raw(1))
|
||||
->from('discussions_tags')
|
||||
->whereRaw('discussion_id = discussions.id');
|
||||
});
|
||||
} else {
|
||||
$id = $this->tags->getIdForSlug($slug);
|
||||
|
||||
$query->orWhereExists(function ($query) use ($id) {
|
||||
$query->select(app('db')->raw(1))
|
||||
->from('discussions_tags')
|
||||
->whereRaw('discussion_id = discussions.id AND tag_id = ?', [$id]);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
<?php namespace Flarum\Categories;
|
||||
<?php namespace Flarum\Tags;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
interface CategoryRepositoryInterface
|
||||
interface TagRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Find all categories, optionally making sure they are visible to a
|
||||
* Find all tags, optionally making sure they are visible to a
|
||||
* certain user.
|
||||
*
|
||||
* @param \Flarum\Core\Models\User|null $user
|
||||
|
@ -14,7 +14,7 @@ interface CategoryRepositoryInterface
|
|||
public function find(User $user = null);
|
||||
|
||||
/**
|
||||
* Get the ID of a category with the given slug.
|
||||
* Get the ID of a tag with the given slug.
|
||||
*
|
||||
* @param string $slug
|
||||
* @param \Flarum\Core\Models\User|null $user
|
42
extensions/tags/src/TagSerializer.php
Normal file
42
extensions/tags/src/TagSerializer.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php namespace Flarum\Tags;
|
||||
|
||||
use Flarum\Api\Serializers\BaseSerializer;
|
||||
|
||||
class TagSerializer extends BaseSerializer
|
||||
{
|
||||
/**
|
||||
* The resource type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'tags';
|
||||
|
||||
/**
|
||||
* Serialize tag attributes to be exposed in the API.
|
||||
*
|
||||
* @param \Flarum\Tags\Tag $tag
|
||||
* @return array
|
||||
*/
|
||||
protected function attributes($tag)
|
||||
{
|
||||
$attributes = [
|
||||
'name' => $tag->name,
|
||||
'description' => $tag->description,
|
||||
'slug' => $tag->slug,
|
||||
'color' => $tag->color,
|
||||
'backgroundUrl' => $tag->background_path,
|
||||
'iconUrl' => $tag->icon_path,
|
||||
'discussionsCount' => (int) $tag->discussions_count,
|
||||
'position' => $tag->position === null ? null : (int) $tag->position,
|
||||
'defaultSort' => $tag->default_sort,
|
||||
'isChild' => (bool) $tag->parent_id
|
||||
];
|
||||
|
||||
return $this->extendAttributes($tag, $attributes);
|
||||
}
|
||||
|
||||
protected function parent()
|
||||
{
|
||||
return $this->hasOne('Flarum\Tags\TagSerializer');
|
||||
}
|
||||
}
|
64
extensions/tags/src/TagsServiceProvider.php
Normal file
64
extensions/tags/src/TagsServiceProvider.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php namespace Flarum\Tags;
|
||||
|
||||
use Flarum\Support\ServiceProvider;
|
||||
use Flarum\Extend\ForumAssets;
|
||||
use Flarum\Extend\EventSubscribers;
|
||||
use Flarum\Extend\Relationship;
|
||||
use Flarum\Extend\SerializeRelationship;
|
||||
use Flarum\Extend\ApiInclude;
|
||||
use Flarum\Extend\Permission;
|
||||
use Flarum\Extend\DiscussionGambit;
|
||||
|
||||
class TagsServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap the application events.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->extend(
|
||||
new ForumAssets([
|
||||
__DIR__.'/../js/dist/extension.js',
|
||||
__DIR__.'/../less/extension.less'
|
||||
]),
|
||||
|
||||
new EventSubscribers([
|
||||
// 'Flarum\Categories\Handlers\DiscussionMovedNotifier',
|
||||
'Flarum\Tags\Handlers\TagPreloader',
|
||||
// 'Flarum\Categories\Handlers\CategorySaver'
|
||||
]),
|
||||
|
||||
new Relationship('Flarum\Core\Models\Discussion', 'tags', function ($model) {
|
||||
return $model->belongsToMany('Flarum\Tags\Tag', 'discussions_tags');
|
||||
}),
|
||||
|
||||
new SerializeRelationship('Flarum\Api\Serializers\DiscussionBasicSerializer', 'hasMany', 'tags', 'Flarum\Tags\TagSerializer'),
|
||||
|
||||
new ApiInclude(['discussions.index', 'discussions.show'], 'tags', true),
|
||||
|
||||
(new Permission('discussion.editTags'))
|
||||
->serialize()
|
||||
->grant(function ($grant, $user) {
|
||||
$grant->where('start_user_id', $user->id);
|
||||
// @todo add limitations to time etc. according to a config setting
|
||||
}),
|
||||
|
||||
new DiscussionGambit('Flarum\Tags\TagGambit')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->bind(
|
||||
'Flarum\Tags\TagRepositoryInterface',
|
||||
'Flarum\Tags\EloquentTagRepository'
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user