Skip to content

Commit

Permalink
FEATURE: improves select-box to support category selection on new topic
Browse files Browse the repository at this point in the history
  • Loading branch information
jjaffeux committed Aug 23, 2017
1 parent 7c15b27 commit 7b4e302
Show file tree
Hide file tree
Showing 18 changed files with 438 additions and 172 deletions.
10 changes: 5 additions & 5 deletions app/assets/javascripts/admin/templates/customize-themes-show.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@

<h3>{{i18n "admin.customize.theme.color_scheme"}}</h3>
<p>{{i18n "admin.customize.theme.color_scheme_select"}}</p>
<p>{{d-select-box content=colorSchemes
textKey="name"
filterable=true
value=colorSchemeId
icon="paint-brush"}}
<p>{{select-box content=colorSchemes
textKey="name"
filterable=true
value=colorSchemeId
icon="paint-brush"}}
{{#if colorSchemeChanged}}
{{d-button action="changeScheme" class="btn-primary btn-small submit-edit" icon="check"}}
{{d-button action="cancelChangeScheme" class="btn-small cancel-edit" icon="times"}}
Expand Down
107 changes: 107 additions & 0 deletions app/assets/javascripts/discourse/components/category-select-box.js.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import SelectBoxComponent from "discourse/components/select-box";
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
import { observes, on } from 'ember-addons/ember-computed-decorators';
import PermissionType from 'discourse/models/permission-type';
import Category from 'discourse/models/category';

export default SelectBoxComponent.extend({
classNames: ["category-select-box"],

textKey: "name",

filterable: true,

castInteger: true,

width: '100%',

@on("willInsertElement")
@observes("selectedContent")
_setHeaderText: function() {
let headerText;

if (Ember.isNone(this.get("selectedContent"))) {
if (this.siteSettings.allow_uncategorized_topics) {
headerText = Ember.get(Category.findUncategorized(), this.get("textKey"));
} else {
headerText = I18n.t("category.choose");
}
} else {
headerText = this.get("selectedContent.text");
}

this.set("headerText", headerText);
},

// original method is kept for compatibility
selectBoxRowTemplate: function() {
return (rowComponent) => this.rowContentTemplate(rowComponent.get("content"));
}.property(),

@observes("scopedCategoryId", "categories")
_scopeCategories() {
let scopedCategoryId = this.get("scopedCategoryId");
const categories = this.get("categories");

// Always scope to the parent of a category, if present
if (scopedCategoryId) {
const scopedCat = Category.findById(scopedCategoryId);
scopedCategoryId = scopedCat.get("parent_category_id") || scopedCat.get("id");
}

const excludeCategoryId = this.get("excludeCategoryId");

const filteredCategories = categories.filter(c => {
const categoryId = c.get("id");
if (scopedCategoryId && categoryId !== scopedCategoryId && c.get("parent_category_id") !== scopedCategoryId) { return false; }
if (excludeCategoryId === categoryId) { return false; }
return c.get("permission") === PermissionType.FULL;
});

this.set("content", filteredCategories);
},

@on("init")
@observes("site.sortedCategories")
_updateCategories() {
if (!this.get("categories")) {
const categories = Discourse.SiteSettings.fixed_category_positions_on_create ?
Category.list() :
Category.listByActivity();
this.set("categories", categories);
}
},

rowContentTemplate(item) {
let category;
let result = '<div class="category-status">';

// If we have no id, but text with the uncategorized name, we can use that badge.
if (Ember.isEmpty(item.id)) {
const uncat = Category.findUncategorized();
if (uncat && uncat.get("name") === item.text) {
category = uncat;
}
} else {
category = Category.findById(parseInt(item.id,10));
}

if (!category) return item.text;
result += categoryBadgeHTML(category, {link: false, allowUncategorized: true, hideParent: true});
const parentCategoryId = category.get("parent_category_id");

if (parentCategoryId) {
result += categoryBadgeHTML(Category.findById(parentCategoryId), {link: false}) + "&nbsp;" + result;
}

result += ` <span class='topic-count'>&times; ${category.get("topic_count")}</span></div>`;

const description = category.get("description");
// TODO wtf how can this be null?;
if (description && description !== 'null') {
result += `<div class="category-desc">${description.substr(0, 200)}${description.length > 200 ? '&hellip;' : ''}</div>`;
}

return result;
}
});

This file was deleted.

96 changes: 79 additions & 17 deletions app/assets/javascripts/discourse/components/select-box.js.es6
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { on, observes } from "ember-addons/ember-computed-decorators";
import { iconHTML } from 'discourse-common/lib/icon-library';

export default Ember.Component.extend({
layoutName: "components/select-box",

classNames: "select-box",

classNameBindings: ["expanded:is-expanded"],
width: 220,

attributeBindings: ['componentStyle:style'],
componentStyle: function() {
return Ember.String.htmlSafe(`width: ${this.get("maxWidth")}px`);
}.property("maxWidth"),
classNameBindings: ["expanded:is-expanded"],

expanded: false,
focused: false,
tabindex: 0,

caretUpIcon: "caret-up",
caretDownIcon: "caret-down",
headerText: null,
icon: null,

value: null,
selectedContent: null,
noContentText: I18n.t("select_box.no_content"),
lastHoveredId: null,

Expand All @@ -43,6 +45,31 @@ export default Ember.Component.extend({

renderBody: false,

castInteger: false,

filterFunction: function() {
return (selectBox) => {
const filter = selectBox.get("filter").toLowerCase();
return _.filter(selectBox.get("content"), (content) => {
return content[selectBox.get("textKey")].toLowerCase().indexOf(filter) > -1;
});
};
},

selectBoxRowTemplate: function() {
return (rowComponent) => {
let template = "";

if (rowComponent.get("content.icon")) {
template += iconHTML(Handlebars.escapeExpression(rowComponent.get("content.icon")));
}

template += `<p class="text">${Handlebars.escapeExpression(rowComponent.get("text"))}</p>`;

return template;
};
}.property(),

init() {
this._super();

Expand All @@ -52,8 +79,7 @@ export default Ember.Component.extend({

this.setProperties({
componentId: this.elementId,
filteredContent: [],
selectedContent: {}
filteredContent: []
});
},

Expand All @@ -71,9 +97,7 @@ export default Ember.Component.extend({
if (Ember.isEmpty(this.get("filter"))) {
this.set("filteredContent", this._remapContent(this.get("content")));
} else {
const filtered = _.filter(this.get("content"), (content) => {
return content[this.get("textKey")].toLowerCase().indexOf(this.get("filter")) > -1;
});
const filtered = this.filterFunction()(this);
this.set("filteredContent", this._remapContent(filtered));
}
},
Expand All @@ -95,18 +119,26 @@ export default Ember.Component.extend({
$(document).off("keydown.select-box");
this.$(".select-box-offscreen").off("focusin.select-box");
this.$(".select-box-offscreen").off("focusout.select-box");
this.$(".select-box-offscreen").off("keydown.select-box");
$(window).off("resize.select-box");
},

@on("didRender")
_configureSelectBoxDOM: function() {
this.$().css("width", this.get("width"));
this.$(".select-box-header").css("height", this.$().height());
this.$(".select-box-filter").css("height", this.$().height());

if (this.get("expanded")) {
this.$(".select-box-body").css('width', this.get("maxWidth"));
this.$(".select-box-filter .filter-query").focus();
this.$(".select-box-body").css('width', this.$().width());
this.$(".select-box-collection").css("max-height", this.get("maxCollectionHeight"));

this._bindTab();
this._applyDirection();
this._positionSelectBoxWrapper();

Ember.run.schedule('afterRender', () => {
this._applyDirection();
this._positionSelectBoxWrapper();
});
} else {
$(document).off("keydown.select-box");
this.$(".select-box-wrapper").hide();
Expand All @@ -124,7 +156,10 @@ export default Ember.Component.extend({

this.set("filteredContent", this._remapContent(this.get("content")));
this._setSelectedContent(this.get("content"));
this.set("headerText", this.get("defaultHeaderText") || this.get("selectedContent.text"));

if (Ember.isNone(this.get("headerText"))) {
this.set("headerText", this.get("selectedContent.text"));
}
},

@on("didInsertElement")
Expand All @@ -143,9 +178,27 @@ export default Ember.Component.extend({
this.set("focused", true);
});

this.$(".select-box-offscreen").on("keydown.select-box", (event) => {
const keyCode = event.keyCode || event.which;

if(keyCode === 13 || keyCode === 40) {
this.setProperties({expanded: true, focused: false});
return false;
}

if(keyCode === 27) {
this.$(".select-box-offscreen").blur();
return false;
}
});

this.$(".select-box-offscreen").on("focusout.select-box", () => {
this.set("focused", false);
});

$(window).on("resize.select-box", () => {
this.set("expanded", false);
});
},

actions: {
Expand All @@ -158,6 +211,10 @@ export default Ember.Component.extend({
},

onSelectRow(id) {
if(this.get("castInteger") === true) {
id = parseInt(id, 10);
}

this.setProperties({
value: id,
expanded: false
Expand All @@ -184,8 +241,13 @@ export default Ember.Component.extend({
},

_normalizeContent(content) {
let id = content[this.get("idKey")];
if(this.get("castInteger") === true) {
id = parseInt(id, 10);
}

return {
id: content[this.get("idKey")],
id,
text: content[this.get("textKey")],
icon: content[this.get("iconKey")]
};
Expand All @@ -204,7 +266,7 @@ export default Ember.Component.extend({
const headerHeight = this.$(".select-box-header").outerHeight();

this.$(".select-box-wrapper").css({
width: this.get("maxWidth"),
width: this.$().width(),
display: "block",
height: headerHeight + this.$(".select-box-body").outerHeight()
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { on, observes } from 'ember-addons/ember-computed-decorators';

export default Ember.Component.extend({
classNames: "select-box-row",

Expand All @@ -9,6 +11,14 @@ export default Ember.Component.extend({

lastHoveredId: null,

@on("init")
@observes("content", "lastHoveredId", "selectedId", "selectBoxRowTemplate")
_updateTemplate: function() {
this.set("isHighlighted", this._isHighlighted());
this.set("text", this.get("content.text"));
this.set("template", this.get("selectBoxRowTemplate")(this));
},

mouseEnter() {
this.sendAction("onHover", this.get("content.id"));
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { observes } from 'ember-addons/ember-computed-decorators';
import DiscourseSelectBoxComponent from "discourse/components/d-select-box";
import SelectBoxComponent from "discourse/components/select-box";

export default SelectBoxComponent.extend({
textKey: "name",

headerText: I18n.t("topic.controls"),

maxCollectionHeight: 300,

export default DiscourseSelectBoxComponent.extend({
init() {
this._super();

this.set("textKey", "name");
this.set("defaultHeaderText", I18n.t("topic.controls"));
this.set("maxCollectionHeight", 300);
this._createContent();
},

Expand Down
2 changes: 1 addition & 1 deletion app/assets/javascripts/discourse/lib/safari-hacks.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ function positioningWorkaround($fixedElement) {

fixedElement.style.top = '0px';

composingTopic = $('#reply-control select.category-combobox').length > 0;
composingTopic = $('#reply-control .category-select-box').length > 0;

const height = calcHeight(composingTopic);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
aria-haspopup="true"
role="button"
aria-labelledby="select-box-input-{{componentId}}"
tabindex={{tabindex}}
/>

{{component selectBoxHeaderComponent
Expand All @@ -29,6 +30,7 @@
{{component selectBoxCollectionComponent
filteredContent=filteredContent
selectBoxRowComponent=selectBoxRowComponent
selectBoxRowTemplate=selectBoxRowTemplate
lastHoveredId=lastHoveredId
onSelectRow=(action "onSelectRow")
onHoverRow=(action "onHoverRow")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{{#each filteredContent as |content|}}
{{component selectBoxRowComponent
content=content
selectBoxRowTemplate=selectBoxRowTemplate
lastHoveredId=lastHoveredId
onSelect=onSelectRow
onHover=onHoverRow
Expand Down
Loading

1 comment on commit 7b4e302

@discoursebot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit has been mentioned on Discourse Meta. There might be relevant details there:

https://meta.discourse.org/t/category-from-new-topic-route-not-displayed-in-composer/68790/1

Please sign in to comment.