mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
Merge pull request #3965 from tgxworld/decorate_category_hashtag
Decorate category hashtag
This commit is contained in:
commit
c2c1aea381
@ -3,6 +3,7 @@ import loadScript from 'discourse/lib/load-script';
|
|||||||
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
|
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
|
||||||
import { showSelector } from "discourse/lib/emoji/emoji-toolbar";
|
import { showSelector } from "discourse/lib/emoji/emoji-toolbar";
|
||||||
import Category from 'discourse/models/category';
|
import Category from 'discourse/models/category';
|
||||||
|
import { SEPARATOR as categoryHashtagSeparator } from 'discourse/lib/category-hashtags';
|
||||||
|
|
||||||
// Our head can be a static string or a function that returns a string
|
// Our head can be a static string or a function that returns a string
|
||||||
// based on input (like for numbered lists).
|
// based on input (like for numbered lists).
|
||||||
@ -255,7 +256,7 @@ export default Ember.Component.extend({
|
|||||||
template: template,
|
template: template,
|
||||||
key: '#',
|
key: '#',
|
||||||
transformComplete(category) {
|
transformComplete(category) {
|
||||||
return Category.slugFor(category, ":");
|
return Category.slugFor(category, categoryHashtagSeparator);
|
||||||
},
|
},
|
||||||
dataSource(term) {
|
dataSource(term) {
|
||||||
return Category.search(term);
|
return Category.search(term);
|
||||||
|
@ -21,7 +21,7 @@ export function categoryBadgeHTML(category, opts) {
|
|||||||
|
|
||||||
var description = get(category, 'description_text'),
|
var description = get(category, 'description_text'),
|
||||||
restricted = get(category, 'read_restricted'),
|
restricted = get(category, 'read_restricted'),
|
||||||
url = Discourse.getURL("/c/") + Discourse.Category.slugFor(category),
|
url = opts.url ? opts.url : Discourse.getURL("/c/") + Discourse.Category.slugFor(category),
|
||||||
href = (opts.link === false ? '' : url),
|
href = (opts.link === false ? '' : url),
|
||||||
tagName = (opts.link === false || opts.link === "false" ? 'span' : 'a'),
|
tagName = (opts.link === false || opts.link === "false" ? 'span' : 'a'),
|
||||||
extraClasses = (opts.extraClasses ? (' ' + opts.extraClasses) : ''),
|
extraClasses = (opts.extraClasses ? (' ' + opts.extraClasses) : ''),
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import Category from 'discourse/models/category';
|
||||||
|
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
|
||||||
|
|
||||||
|
export const SEPARATOR = ":";
|
||||||
|
|
||||||
|
export function findCategoryByHashtagSlug(hashtagSlug) {
|
||||||
|
if (hashtagSlug.indexOf('#') === 0) hashtagSlug = hashtagSlug.slice(1);
|
||||||
|
return Category.findBySlug.apply(null, hashtagSlug.split(SEPARATOR, 2).reverse());
|
||||||
|
};
|
||||||
|
|
||||||
|
export function replaceSpan($elem, categorySlug, categoryLink) {
|
||||||
|
const category = findCategoryByHashtagSlug(categorySlug);
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
$elem.replaceWith(categorySlug);
|
||||||
|
} else {
|
||||||
|
$elem.replaceWith(categoryBadgeHTML(
|
||||||
|
category, { url: categoryLink, allowUncategorized: true }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function decorateLinks($elems) {
|
||||||
|
$elems.each((_, elem) => replaceSpan($(elem), elem.text, elem.href));
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,10 @@
|
|||||||
|
import { replaceSpan } from 'discourse/lib/category-hashtags';
|
||||||
|
|
||||||
const validCategoryHashtags = {};
|
const validCategoryHashtags = {};
|
||||||
const checkedCategoryHashtags = [];
|
const checkedCategoryHashtags = [];
|
||||||
const testedKey = 'tested';
|
const testedKey = 'tested';
|
||||||
const testedClass = `hashtag-${testedKey}`;
|
const testedClass = `hashtag-${testedKey}`;
|
||||||
|
|
||||||
function replaceSpan($elem, categorySlug, categoryLink) {
|
|
||||||
$elem.replaceWith(`<a href="${categoryLink}" class="hashtag">#${categorySlug}</a>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFound($hashtags, categorySlugs) {
|
function updateFound($hashtags, categorySlugs) {
|
||||||
Ember.run.schedule('afterRender', () => {
|
Ember.run.schedule('afterRender', () => {
|
||||||
$hashtags.each((index, hashtag) => {
|
$hashtags.each((index, hashtag) => {
|
||||||
|
@ -3,6 +3,7 @@ import { number } from 'discourse/lib/formatter';
|
|||||||
import DiscourseURL from 'discourse/lib/url';
|
import DiscourseURL from 'discourse/lib/url';
|
||||||
import { default as computed, on } from 'ember-addons/ember-computed-decorators';
|
import { default as computed, on } from 'ember-addons/ember-computed-decorators';
|
||||||
import { fmt } from 'discourse/lib/computed';
|
import { fmt } from 'discourse/lib/computed';
|
||||||
|
import { decorateLinks as decorateCategoryHashtagLinks } from 'discourse/lib/category-hashtags';
|
||||||
|
|
||||||
const DAY = 60 * 50 * 1000;
|
const DAY = 60 * 50 * 1000;
|
||||||
|
|
||||||
@ -75,6 +76,7 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
|||||||
_cookedWasChanged() {
|
_cookedWasChanged() {
|
||||||
this.trigger('postViewUpdated', this.$());
|
this.trigger('postViewUpdated', this.$());
|
||||||
this._insertQuoteControls();
|
this._insertQuoteControls();
|
||||||
|
this._decorateCategoryHashtagLinks();
|
||||||
},
|
},
|
||||||
|
|
||||||
mouseUp(e) {
|
mouseUp(e) {
|
||||||
@ -318,6 +320,7 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
|||||||
const $post = this.$(),
|
const $post = this.$(),
|
||||||
postNumber = this.get('post').get('post_number');
|
postNumber = this.get('post').get('post_number');
|
||||||
|
|
||||||
|
this._decorateCategoryHashtagLinks();
|
||||||
this._showLinkCounts();
|
this._showLinkCounts();
|
||||||
|
|
||||||
ScreenTrack.current().track($post.prop('id'), postNumber);
|
ScreenTrack.current().track($post.prop('id'), postNumber);
|
||||||
@ -375,7 +378,12 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
|||||||
cooked.unhighlight();
|
cooked.unhighlight();
|
||||||
this._highlighted = false;
|
this._highlighted = false;
|
||||||
}
|
}
|
||||||
}.observes('searchService.highlightTerm', 'cooked')
|
}.observes('searchService.highlightTerm', 'cooked'),
|
||||||
|
|
||||||
|
_decorateCategoryHashtagLinks() {
|
||||||
|
const $elems = this.$('.cooked a.hashtag');
|
||||||
|
if ($elems.length > 0) decorateCategoryHashtagLinks($elems);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default PostView;
|
export default PostView;
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
color: $primary !important;
|
color: $primary !important;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
vertical-align: text-top;
|
vertical-align: text-top;
|
||||||
margin-top: -3px; //vertical alignment fix
|
margin-top: -2px; //vertical alignment fix
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -38,21 +38,18 @@
|
|||||||
.extra-info-wrapper & {
|
.extra-info-wrapper & {
|
||||||
color: $header-primary !important;
|
color: $header-primary !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-category-parent-bg, .badge-category-bg {
|
.badge-category-parent-bg, .badge-category-bg {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: "\a0";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "\a0";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
&.bullet { //bullet category style
|
&.bullet { //bullet category style
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
@ -71,31 +68,30 @@
|
|||||||
.extra-info-wrapper & {
|
.extra-info-wrapper & {
|
||||||
color: $header-primary !important;
|
color: $header-primary !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-category-parent-bg, .badge-category-bg {
|
.badge-category-parent-bg, .badge-category-bg {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: "\a0";
|
content: "\a0";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
&.badge-category-parent-bg { //subcategory style
|
&.badge-category-parent-bg { //subcategory style
|
||||||
width: 5px;
|
width: 5px;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
& + .badge-category-bg {
|
& + .badge-category-bg {
|
||||||
width: 5px;
|
width: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
&.box { //box category style (apply custom widths to the wrapper, not the children)
|
&.box { //box category style (apply custom widths to the wrapper, not the children)
|
||||||
@ -134,6 +130,59 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin cooked-badge-bullet($length, $offset:0px) {
|
||||||
|
.badge-wrapper.bullet {
|
||||||
|
span {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.badge-category-bg {
|
||||||
|
width: $length;
|
||||||
|
height: $length;
|
||||||
|
top: $offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.badge-category-parent-bg {
|
||||||
|
width: $length / 2;
|
||||||
|
height: $length;
|
||||||
|
top: $offset;
|
||||||
|
|
||||||
|
& + .badge-category-bg {
|
||||||
|
width: $length / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cooked, .d-editor-preview {
|
||||||
|
p .badge-wrapper.bullet {
|
||||||
|
margin: 0px 2.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { @include cooked-badge-bullet(22px) }
|
||||||
|
h2 { @include cooked-badge-bullet(17px) }
|
||||||
|
h3 { @include cooked-badge-bullet(14px) }
|
||||||
|
h4 { @include cooked-badge-bullet(12px) }
|
||||||
|
h5 { @include cooked-badge-bullet(10px, -1.1px) }
|
||||||
|
h6 { @include cooked-badge-bullet(9px, -1.5px) }
|
||||||
|
|
||||||
|
.badge-wrapper.box {
|
||||||
|
span {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-notification.clicks {
|
||||||
|
display: inline-block;
|
||||||
|
overflow: visible;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-category-bg {
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Category badge dropdown
|
// Category badge dropdown
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ module CategoryHashtag
|
|||||||
|
|
||||||
class_methods do
|
class_methods do
|
||||||
def query_from_hashtag_slug(category_slug)
|
def query_from_hashtag_slug(category_slug)
|
||||||
parent_slug, child_slug = category_slug.split(":", 2)
|
parent_slug, child_slug = category_slug.split(SEPARATOR, 2)
|
||||||
|
|
||||||
category = Category.where(slug: parent_slug, parent_category_id: nil)
|
category = Category.where(slug: parent_slug, parent_category_id: nil)
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ describe CategoryHashtag do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should return the right result for a parent and child category slug" do
|
it "should return the right result for a parent and child category slug" do
|
||||||
expect(Category.query_from_hashtag_slug("#{parent_category.slug}:#{child_category.slug}"))
|
expect(Category.query_from_hashtag_slug("#{parent_category.slug}#{CategoryHashtag::SEPARATOR}#{child_category.slug}"))
|
||||||
.to eq(child_category)
|
.to eq(child_category)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ describe CategoryHashtag do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should return nil for incorrect parent and child category slug" do
|
it "should return nil for incorrect parent and child category slug" do
|
||||||
expect(Category.query_from_hashtag_slug("random-slug:random-slug")).to eq(nil)
|
expect(Category.query_from_hashtag_slug("random-slug#{CategoryHashtag::SEPARATOR}random-slug")).to eq(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
22
test/javascripts/lib/category-hashtags-test.js.es6
Normal file
22
test/javascripts/lib/category-hashtags-test.js.es6
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import createStore from 'helpers/create-store';
|
||||||
|
import Category from 'discourse/models/category';
|
||||||
|
import { findCategoryByHashtagSlug } from "discourse/lib/category-hashtags";
|
||||||
|
|
||||||
|
module("lib:category-hashtags");
|
||||||
|
|
||||||
|
test('findCategoryByHashtagSlug', () => {
|
||||||
|
const store = createStore();
|
||||||
|
|
||||||
|
const parentCategory = store.createRecord('category', { slug: 'test1' });
|
||||||
|
|
||||||
|
const childCategory = store.createRecord('category', {
|
||||||
|
slug: 'test2', parentCategory: parentCategory
|
||||||
|
});
|
||||||
|
|
||||||
|
sandbox.stub(Category, 'list').returns([parentCategory, childCategory]);
|
||||||
|
|
||||||
|
equal(findCategoryByHashtagSlug('test1'), parentCategory, 'returns the right category');
|
||||||
|
equal(findCategoryByHashtagSlug('test1:test2'), childCategory, 'returns the right category');
|
||||||
|
equal(findCategoryByHashtagSlug('#test1'), parentCategory, 'returns the right category');
|
||||||
|
equal(findCategoryByHashtagSlug('#test1:test2'), childCategory, 'returns the right category');
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user