Merge branch 'master' into beta

This commit is contained in:
Robin Ward 2017-08-31 14:54:57 -04:00
commit 1e5d451cb1
350 changed files with 7405 additions and 3980 deletions

View File

@ -42,6 +42,7 @@
"invisible":true,
"asyncRender":true,
"selectDropdown":true,
"selectBox":true,
"asyncTestDiscourse":true,
"fixture":true,
"find":true,
@ -92,7 +93,9 @@
"wrap-iife": [
2,
"inside"
]
],
"no-mixed-spaces-and-tabs": 2,
"no-trailing-spaces": 2
},
"parser": "babel-eslint"
}

5
.gitignore vendored
View File

@ -44,6 +44,7 @@ config/discourse.conf
/logfile
log/
bootsnap-load-path-cache
bootsnap-compile-cache/
# Ignore plugins except for the bundled ones.
/plugins/*
@ -117,3 +118,7 @@ vendor/bundle/*
#ignore jetbrains ide file
*.iml
# ignore nodejs files
/node_modules
/package-lock.json

View File

@ -5,8 +5,9 @@ env:
- DISCOURSE_HOSTNAME=www.example.com
- RUBY_GC_MALLOC_LIMIT=50000000
matrix:
- "RAILS_MASTER=0 QUNIT_RUN=0"
- "RAILS_MASTER=0 QUNIT_RUN=1"
- "RAILS_MASTER=0 QUNIT_RUN=0 RUN_LINT=0"
- "RAILS_MASTER=0 QUNIT_RUN=1 RUN_LINT=0"
- "RAILS_MASTER=0 QUNIT_RUN=0 RUN_LINT=1"
addons:
postgresql: 9.5
@ -36,27 +37,35 @@ cache:
- vendor/bundle
before_install:
- gem install bundler rubocop
- gem install bundler
- git clone --depth=1 https://github.com/discourse/discourse-backup-uploads-to-s3.git plugins/discourse-backup-uploads-to-s3
- git clone --depth=1 https://github.com/discourse/discourse-spoiler-alert.git plugins/discourse-spoiler-alert
- git clone --depth=1 https://github.com/discourse/discourse-cakeday.git plugins/discourse-cakeday
- git clone --depth=1 https://github.com/discourse/discourse-canned-replies.git plugins/discourse-canned-replies
- git clone --depth=1 https://github.com/discourse/discourse-slack-official.git plugins/discourse-slack-official
- yarn global add eslint babel-eslint
- eslint app/assets/javascripts
- eslint --ext .es6 app/assets/javascripts
- eslint --ext .es6 test/javascripts
- eslint --ext .es6 plugins/**/assets/javascripts
- eslint --ext .es6 plugins/**/test/javascripts
- eslint test/javascripts
- rubocop --parallel
before_script:
- bundle exec rake db:create db:migrate
install:
- bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails seed-fu; fi"
- bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi"
- bash -c "if [ '$RUN_LINT' == '1' ]; then yarn global add eslint babel-eslint; fi"
script:
- bash -c "if [ '$QUNIT_RUN' == '0' ]; then bundle exec rspec && bundle exec rake plugin:spec; else LOAD_PLUGINS=1 bundle exec rake qunit:test['300000']; fi"
- |
bash -c "
if [ '$RUN_LINT' == '1' ]; then
bundle exec rubocop --parallel && \
eslint --ext .es6 app/assets/javascripts && \
eslint --ext .es6 test/javascripts && \
eslint --ext .es6 plugins/**/assets/javascripts && \
eslint --ext .es6 plugins/**/test/javascripts && \
eslint app/assets/javascripts test/javascripts
else
bundle exec rake db:create db:migrate
if [ '$QUNIT_RUN' == '1' ]; then
LOAD_PLUGINS=1 bundle exec rake qunit:test['300000']
else
bundle exec rspec && bundle exec rake plugin:spec
fi
fi
"

View File

@ -140,6 +140,7 @@ group :test, :development do
gem 'rspec-html-matchers'
gem 'pry-nav'
gem 'byebug', require: ENV['RM_INFO'].nil?
gem 'rubocop', require: false
end
group :development do
@ -148,7 +149,6 @@ group :development do
gem 'binding_of_caller'
gem 'annotate'
gem 'foreman', require: false
gem 'rubocop', require: false
end
# this is an optional gem, it provides a high performance replacement

View File

@ -9,7 +9,7 @@
{{input value=buffered.path_whitelist placeholder="/blog/.*" enter="save" class="path-whitelist"}}
</td>
<td>
{{category-chooser value=categoryId}}
{{category-select-box value=categoryId class="small"}}
</td>
<td>
{{d-button icon="check" action="save" class="btn-primary" disabled=cantSave}}

View File

@ -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"}}

View File

@ -111,8 +111,13 @@
{{/unless}}
<div>
<label for="alias">{{i18n 'groups.alias_levels.title'}}</label>
{{combo-box name="alias" valueAttribute="value" value=model.alias_level content=aliasLevelOptions}}
<label for="alias">{{i18n 'groups.alias_levels.mentionable'}}</label>
{{combo-box name="alias" valueAttribute="value" value=model.mentionable_level content=aliasLevelOptions}}
</div>
<div>
<label for="alias">{{i18n 'groups.alias_levels.messageable'}}</label>
{{combo-box name="alias" valueAttribute="value" value=model.messageable_level content=aliasLevelOptions}}
</div>
<div>

View File

@ -15,14 +15,14 @@
<div class="admin-nav pull-left">
<ul class="nav nav-stacked">
{{#each model as |category|}}
{{#link-to 'adminSiteSettingsCategory' category.nameKey tagName='li' class=category.nameKey}}
<li class="{{category.nameKey}}">
{{#link-to 'adminSiteSettingsCategory' category.nameKey class=category.nameKey}}
{{category.name}}
{{#if filtered}}
{{#if category.count}}<span class="count">({{category.count}})</span>{{/if}}
{{/if}}
{{/link-to}}
{{/link-to}}
</li>
{{/each}}
</ul>
</div>

View File

@ -14,12 +14,12 @@
<div class="admin-nav pull-left">
<ul class="nav nav-stacked">
{{#each model as |action|}}
{{#link-to 'adminWatchedWords.action' action.nameKey tagName='li' class=action.nameKey}}
<li class="{{action.nameKey}}">
{{#link-to 'adminWatchedWords.action' action.nameKey}}
{{action.name}}
{{#if action.count}}<span class="count">({{action.count}})</span>{{/if}}
{{/link-to}}
{{/link-to}}
</li>
{{/each}}
</ul>
</div>

View File

@ -0,0 +1,28 @@
import { ajax } from 'discourse/lib/ajax';
import BadgeSelectController from 'discourse/mixins/badge-select-controller';
export default Ember.Component.extend(BadgeSelectController, {
classNames: ["badge-title"],
saved: false,
saving: false,
actions: {
save() {
this.setProperties({ saved: false, saving: true });
ajax(this.get('user.path') + "/preferences/badge_title", {
type: "PUT",
data: { user_badge_id: this.get('selectedUserBadgeId') }
}).then(() => {
this.setProperties({
saved: true,
saving: false,
"user.title": this.get('selectedUserBadge.badge.name')
});
}, () => {
bootbox.alert(I18n.t('generic_error'));
});
}
}
});

View File

@ -72,7 +72,7 @@ export default Ember.Component.extend({
if (color || textColor) {
let style = "";
if (color) {
if (color) {
if (categoryStyle === "bar") {
style += `border-color: #${color};`;
} else if (categoryStyle === "box") {

View File

@ -0,0 +1,156 @@
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"],
selectBoxRowComponent: "category-select-box/category-select-box-row",
textKey: "name",
filterable: true,
castInteger: true,
clearable: true,
allowUncategorized: null,
init() {
this._super();
if (!Ember.isNone(this.get("categories"))) {
this.set("content", this.get("categories"));
this._scopeCategories();
}
},
filterFunction: function(content) {
const _matchFunction = (filter, text) => {
return text.toLowerCase().indexOf(filter) > -1;
};
return (selectBox) => {
const filter = selectBox.get("filter").toLowerCase();
return _.filter(content, (c) => {
const category = Category.findById(c[selectBox.get("idKey")]);
const text = c[selectBox.get("textKey")];
if (category && category.get("parentCategory")) {
const categoryName = category.get("parentCategory.name");
return _matchFunction(filter, text) || _matchFunction(filter, categoryName);
} else {
return _matchFunction(filter, text);
}
});
};
},
@on("init")
@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").htmlSafe();
}
} else {
headerText = this.get("selectedContent.text");
}
this.set("headerText", headerText);
},
templateForRow: 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; }
if (this.get("allowUncategorized") === false && c.get("isUncategorizedCategory")) { return false; }
if (this.get("allowUncategorized") !== true) {
if (!this.siteSettings.allow_uncategorized_topics && c.get("isUncategorizedCategory")) {
return false;
}
}
return c.get("permission") === PermissionType.FULL;
});
this.set("content", filteredCategories);
},
@on("didRender")
_bindComposerResizing() {
this.appEvents.on("composer:resized", this, this.applyDirection);
},
@on("willDestroyElement")
_unbindComposerResizing() {
this.appEvents.off("composer:resized");
},
@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;
// 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;
let result = categoryBadgeHTML(category, {link: false, allowUncategorized: true, hideParent: true});
const parentCategoryId = category.get("parent_category_id");
if (parentCategoryId) {
result = `<div class="category-status">${categoryBadgeHTML(Category.findById(parentCategoryId), {link: false})}&nbsp;${result}`;
} else {
result = `<div class="category-status">${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;
}
});

View File

@ -0,0 +1,13 @@
import computed from 'ember-addons/ember-computed-decorators';
import SelectBoxRowComponent from "discourse/components/select-box/select-box-row";
import Category from "discourse/models/category";
export default SelectBoxRowComponent.extend({
classNameBindings: ["isUncategorized"],
@computed("content")
isUncategorized(content) {
const category = Category.findById(content.id);
return category.get("isUncategorizedCategory");
}
});

View File

@ -13,6 +13,9 @@ import { tinyAvatar,
displayErrorForUpload,
getUploadMarkdown,
validateUploadedFiles } from 'discourse/lib/utilities';
import { lookupCachedUploadUrl,
lookupUncachedUploadUrls,
cacheShortUploadUrl } from 'pretty-text/image-short-url';
export default Ember.Component.extend({
classNames: ['wmd-controls'],
@ -191,6 +194,24 @@ export default Ember.Component.extend({
$oneboxes.each((_, o) => load(o, refresh, ajax, this.currentUser.id));
},
_loadShortUrls($images) {
const urls = _.map($images, img => $(img).data('orig-src'));
lookupUncachedUploadUrls(urls, ajax).then(() => this._loadCachedShortUrls($images));
},
_loadCachedShortUrls($images) {
$images.each((idx, image) => {
let $image = $(image);
let url = lookupCachedUploadUrl($image.data('orig-src'));
if (url) {
$image.removeAttr('data-orig-src');
if (url !== "missing") {
$image.attr('src', url);
}
}
});
},
_warnMentionedGroups($preview) {
Ember.run.scheduleOnce('afterRender', () => {
var found = this.get('warnedGroupMentions') || [];
@ -312,6 +333,7 @@ export default Ember.Component.extend({
if (upload && upload.url) {
if (!this._xhr || !this._xhr._userCancelled) {
const markdown = getUploadMarkdown(upload);
cacheShortUploadUrl(upload.short_url, upload.url);
this.appEvents.trigger('composer:replace-text', uploadPlaceholder, markdown);
this._resetUpload(false);
} else {
@ -579,6 +601,19 @@ export default Ember.Component.extend({
Ember.run.debounce(this, this._loadOneboxes, $oneboxes, 450);
}
// Short upload urls
let $shortUploadUrls = $('img[data-orig-src]');
if ($shortUploadUrls.length > 0) {
this._loadCachedShortUrls($shortUploadUrls);
$shortUploadUrls = $('img[data-orig-src]');
if ($shortUploadUrls.length > 0) {
// this is carefully batched so we can do an leading debounce (trigger right away)
Ember.run.debounce(this, this._loadShortUrls, $shortUploadUrls, 450, true);
}
}
let inline = {};
$('a.inline-onebox-loading', $preview).each(function(index, link) {
let $link = $(link);

View File

@ -170,6 +170,7 @@ export default Ember.Component.extend({
const queuedForTyping = this.get('queuedForTyping');
composer.store.find('composer-message', args).then(messages => {
if (this.isDestroying || this.isDestroyed) { return; }
// Checking composer messages on replies can give us a list of links to check for
// duplicates

View File

@ -1,10 +1,17 @@
export default Ember.Component.extend({
classNames: ['modal-body'],
fixed: false,
didInsertElement() {
this._super();
$('#modal-alert').hide();
$('#discourse-modal').modal('show');
let fixedParent = this.$().closest('.d-modal.fixed-modal');
if (fixedParent.length) {
this.set('fixed', true);
fixedParent.modal('show');
}
Ember.run.scheduleOnce('afterRender', this, this._afterFirstRender);
this.appEvents.on('modal-body:flash', msg => this._flash(msg));
},
@ -28,7 +35,14 @@ export default Ember.Component.extend({
}
}
this.appEvents.trigger('modal:body-shown', this.getProperties('title', 'rawTitle'));
this.appEvents.trigger(
'modal:body-shown',
this.getProperties(
'title',
'rawTitle',
'fixed'
)
);
},
_flash(msg) {

View File

@ -1,10 +1,20 @@
import { on } from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
elementId: 'discourse-modal',
classNameBindings: [':modal', 'modalClass'],
classNameBindings: [':modal', ':d-modal', 'modalClass', 'modalStyle'],
attributeBindings: ['data-keyboard'],
init() {
this._super(...arguments);
// If we need to render a second modal for any reason, we can't
// use `elementId`
if (this.get('modalStyle') !== 'inline-modal') {
this.set('elementId', 'discourse-modal');
this.set('modalStyle', 'fixed-modal');
}
},
// We handle ESC ourselves
'data-keyboard': 'false',
@ -17,7 +27,11 @@ export default Ember.Component.extend({
});
this.appEvents.on('modal:body-shown', data => {
this.$().removeClass('hidden');
if (this.isDestroying || this.isDestroyed) { return; }
if (data.fixed) {
this.$().removeClass('hidden');
}
if (data.title) {
this.set('title', I18n.t(data.title));
} else if (data.rawTitle) {

View File

@ -1,7 +0,0 @@
import SelectBoxComponent from "discourse/components/select-box";
export default SelectBoxComponent.extend({
layoutName: "components/select-box",
classNames: "discourse"
});

View File

@ -21,6 +21,8 @@ export function resetCache() {
let $picker, $filter, $results, $list, scrollPosition, $visibleSections, _checkTimeout;
export default Ember.Component.extend({
automaticPositioning: true,
willDestroyElement() {
this._super();
@ -448,7 +450,7 @@ export default Ember.Component.extend({
$picker.css(_.merge(attributes, options));
};
if(Ember.testing) {
if(Ember.testing || this.get("automaticPositioning") === false) {
desktopPositioning();
return;
}

View File

@ -1,6 +1,6 @@
export default Ember.Component.extend({
didInsertElement() {
this._super();
$('#discourse-modal').modal('hide').addClass('hidden');
$('.d-modal.fixed-modal').modal('hide').addClass('hidden');
}
});

View File

@ -6,6 +6,8 @@ export default DropdownButton.extend({
descriptionKey: 'help',
classNames: ['pinned-options'],
title: '',
buttonExtraClasses: 'btn-icon-text',
longDescription: function(){
const topic = this.get('topic');
const globally = topic.get('pinned_globally') ? '_globally' : '';

View File

@ -1,26 +1,33 @@
import { on, observes } from "ember-addons/ember-computed-decorators";
import computed 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"],
attributeBindings: ['componentStyle:style'],
componentStyle: function() {
return Ember.String.htmlSafe(`width: ${this.get("maxWidth")}px`);
}.property("maxWidth"),
expanded: false,
focused: false,
filterFocused: false,
renderBody: false,
wrapper: true,
tabindex: 0,
scrollableParentSelector: ".modal-body",
caretUpIcon: "caret-up",
caretDownIcon: "caret-down",
headerText: null,
headerText: I18n.t("select_box.default_header_text"),
dynamicHeaderText: true,
icon: null,
clearable: false,
value: null,
noContentText: I18n.t("select_box.no_content"),
lastHoveredId: null,
selectedContent: null,
noContentLabel: I18n.t("select_box.no_content"),
lastHovered: null,
clearSelectionLabel: null,
idKey: "id",
textKey: "text",
@ -36,46 +43,201 @@ export default Ember.Component.extend({
selectBoxHeaderComponent: "select-box/select-box-header",
selectBoxCollectionComponent: "select-box/select-box-collection",
minWidth: 220,
maxCollectionHeight: 200,
maxWidth: 200,
verticalOffset: 0,
horizontalOffset: 0,
renderBody: false,
castInteger: false,
filterFunction: function(content) {
return (selectBox) => {
const filter = selectBox.get("filter").toLowerCase();
return _.filter(content, (c) => {
return c[selectBox.get("textKey")].toLowerCase().indexOf(filter) > -1;
});
};
},
titleForRow: function() {
return (rowComponent) => {
return rowComponent.get(`content.${this.get("textKey")}`);
};
}.property(),
shouldHighlightRow: function() {
return (rowComponent) => {
if (Ember.isNone(this.get("value")) && Ember.isNone(this.get("lastHovered"))) {
return false;
}
const id = this._castInteger(rowComponent.get(`content.${this.get("idKey")}`));
if (Ember.isNone(this.get("lastHovered"))) {
return id === this.get("value");
} else {
return id === this.get("lastHovered");
}
};
}.property(),
templateForRow: function() {
return (rowComponent) => {
let template = "";
if (rowComponent.get("content.icon")) {
template += iconHTML(Handlebars.escapeExpression(rowComponent.get("content.icon")));
}
const text = rowComponent.get(`content.${this.get("textKey")}`);
template += `<p class="text">${Handlebars.escapeExpression(text)}</p>`;
return template;
};
}.property(),
applyDirection() {
const offsetTop = this.$()[0].getBoundingClientRect().top;
const windowHeight = $(window).height();
const headerHeight = this.$(".select-box-header").outerHeight(false);
const filterHeight = this.$(".select-box-filter").outerHeight(false);
if (windowHeight - (offsetTop + this.get("maxCollectionHeight") + filterHeight + headerHeight) < 0) {
this.$().addClass("is-reversed");
this.$(".select-box-body").css({
left: this.get("horizontalOffset"),
top: "auto",
bottom: headerHeight + this.get("verticalOffset")
});
} else {
this.$().removeClass("is-reversed");
this.$(".select-box-body").css({
left: this.get("horizontalOffset"),
top: headerHeight + this.get("verticalOffset"),
bottom: "auto"
});
}
},
init() {
this._super();
if (!this.get("content")) {
this.set("content", []);
const content = this.getWithDefault("content", []);
this.set("content", content);
if (this.site.isMobileDevice) {
this.set("filterable", false);
}
this.setProperties({
componentId: this.elementId,
filteredContent: [],
selectedContent: {}
value: this._castInteger(this.get("value")),
componentId: this.elementId
});
},
@observes("value")
_valueChanged: function() {
if (Ember.isNone(this.get("value"))) {
this.set("lastHoveredId", null);
}
this.set("filteredContent", this._remapContent(this.get("content")));
@on("willDestroyElement")
_removeDocumentListeners: function() {
$(document).off("click.select-box", "keydown.select-box");
$(window).off("resize.select-box");
},
@observes("filter")
_filterChanged: function() {
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;
});
this.set("filteredContent", this._remapContent(filtered));
@on("willDestroyElement")
_unbindEvents: function() {
this.$(".select-box-offscreen").off(
"focusin.select-box",
"focusout.select-box",
"keydown.select-box"
);
this.$(".filter-query").off("focusin.select-box", "focusout.select-box");
},
@on("didRender")
_configureSelectBoxDOM: function() {
if (this.get("scrollableParent").length === 1) {
this._removeFixedPosition();
}
this.$().css("min-width", this.get("minWidth"));
const computedWidth = this.$().outerWidth(false);
const computedHeight = this.$().outerHeight(false);
this.$(".select-box-header").css("height", computedHeight);
this.$(".select-box-filter").css("height", computedHeight);
if (this.get("expanded")) {
if (this.get("scrollableParent").length === 1) {
this._applyFixedPosition(computedWidth, computedHeight);
}
this.$(".select-box-body").css("width", computedWidth);
this.$(".select-box-collection").css("max-height", this.get("maxCollectionHeight"));
this.applyDirection();
if (this.get("wrapper")) {
this._positionSelectBoxWrapper();
}
} else {
if (this.get("wrapper")) {
this.$(".select-box-wrapper").hide();
}
}
},
@on("didRender")
_setupDocumentListeners: function() {
$(document)
.on("click.select-box", (event) => {
if (this.isDestroying || this.isDestroyed) { return; }
const $element = this.$();
const $target = $(event.target);
if (!$target.closest($element).length) {
this.set("expanded", false);
}
})
.on("keydown.select-box", (event) => {
const keyCode = event.keyCode || event.which;
if (this.get("expanded") && keyCode === 9) {
this.set("expanded", false);
}
});
$(window).on("resize.select-box", () => this.set("expanded", false) );
},
@on("didInsertElement")
_bindEvents: function() {
this.$(".select-box-offscreen")
.on("focusin.select-box", () => this.set("focused", true) )
.on("focusout.select-box", () => this.set("focused", false) );
this.$(".filter-query")
.on("focusin.select-box", () => this.set("filterFocused", true) )
.on("focusout.select-box", () => this.set("filterFocused", false) );
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;
}
if (keyCode >= 65 && keyCode <= 90) {
this.setProperties({expanded: true, focused: false});
Ember.run.schedule("afterRender", () => {
this.$(".filter-query").focus().val(String.fromCharCode(keyCode));
});
}
});
},
@observes("expanded")
@ -83,69 +245,52 @@ export default Ember.Component.extend({
if (this.get("expanded")) {
this.setProperties({ focused: false, renderBody: true });
if (Ember.isNone(this.get("lastHoveredId"))) {
this.set("lastHoveredId", this.get("value"));
if (this.get("filterable")) {
Ember.run.schedule("afterRender", () => this.$(".filter-query").focus());
}
};
},
@on("willDestroyElement")
_unbindEvents: function() {
$(document).off("click.select-box");
$(document).off("keydown.select-box");
this.$(".select-box-offscreen").off("focusin.select-box");
this.$(".select-box-offscreen").off("focusout.select-box");
},
@on("didRender")
_configureSelectBoxDOM: function() {
if (this.get("expanded")) {
this.$(".select-box-body").css('width', this.get("maxWidth"));
this.$(".select-box-filter .filter-query").focus();
this.$(".select-box-collection").css("max-height", this.get("maxCollectionHeight"));
this._bindTab();
this._applyDirection();
this._positionSelectBoxWrapper();
} else {
$(document).off("keydown.select-box");
this.$(".select-box-wrapper").hide();
}
},
@observes("content.[]")
@on("didReceiveAttrs")
_contentChanged: function() {
if (!Ember.isNone(this.get("value"))) {
this.set("lastHoveredId", this.get("content")[this.get("idKey")]);
} else {
this.set("lastHoveredId", null);
@computed("value", "content.[]")
selectedContent(value, content) {
if (Ember.isNone(value)) {
return null;
}
this.set("filteredContent", this._remapContent(this.get("content")));
this._setSelectedContent(this.get("content"));
this.set("headerText", this.get("defaultHeaderText") || this.get("selectedContent.text"));
return content.find((c) => {
return this._castInteger(c[this.get("idKey")]) === value;
});
},
@on("didInsertElement")
_bindEvents: function() {
$(document).on("click.select-box", (event) => {
const clickOutside = $(event.target).parents(".select-box").attr("id") !== this.$().attr("id");
if (this.get("expanded") && clickOutside) {
this.setProperties({
expanded: false,
focused: false
});
}
});
@computed("headerText", "dynamicHeaderText", "selectedContent", "textKey", "clearSelectionLabel")
generatedHeadertext(headerText, dynamic, selectedContent, textKey, clearSelectionLabel) {
if (dynamic && !Ember.isNone(selectedContent)) {
return selectedContent[textKey];
}
this.$(".select-box-offscreen").on("focusin.select-box", () => {
this.set("focused", true);
});
if (dynamic && Ember.isNone(selectedContent) && !Ember.isNone(clearSelectionLabel)) {
return I18n.t(clearSelectionLabel);
}
this.$(".select-box-offscreen").on("focusout.select-box", () => {
this.set("focused", false);
});
return headerText;
},
@computed("content.[]", "filter")
filteredContent(content, filter) {
let filteredContent;
if (Ember.isEmpty(filter)) {
filteredContent = content;
} else {
filteredContent = this.filterFunction(content)(this);
}
return filteredContent;
},
@computed("scrollableParentSelector")
scrollableParent(scrollableParentSelector) {
return this.$().parents(scrollableParentSelector).first();
},
actions: {
@ -157,80 +302,65 @@ export default Ember.Component.extend({
this.set("filter", filter);
},
onSelectRow(id) {
onSelectRow(content) {
this.setProperties({
value: id,
value: this._castInteger(content[this.get("idKey")]),
expanded: false
});
},
onHoverRow(id) {
this.set("lastHoveredId", id);
onClearSelection() {
this.setProperties({ value: null, expanded: false });
},
onHoverRow(content) {
this.set("lastHovered", this._castInteger(content[this.get("idKey")]));
}
},
_setSelectedContent(content) {
const selectedContent = content.find((c) => {
return c[this.get("idKey")] === this.get("value");
});
if (!Ember.isNone(selectedContent)) {
this.set("selectedContent", this._normalizeContent(selectedContent));
}
},
_remapContent(content) {
return content.map(c => this._normalizeContent(c));
},
_normalizeContent(content) {
return {
id: content[this.get("idKey")],
text: content[this.get("textKey")],
icon: content[this.get("iconKey")]
};
},
_bindTab() {
$(document).on("keydown.select-box", (event) => {
const keyCode = event.keyCode || event.which;
if (keyCode === 9) {
this.set("expanded", false);
}
});
},
_positionSelectBoxWrapper() {
const headerHeight = this.$(".select-box-header").outerHeight();
const headerHeight = this.$(".select-box-header").outerHeight(false);
this.$(".select-box-wrapper").css({
width: this.get("maxWidth"),
width: this.$().width(),
display: "block",
height: headerHeight + this.$(".select-box-body").outerHeight()
height: headerHeight + this.$(".select-box-body").outerHeight(false)
});
},
_applyDirection() {
this.$().removeClass("is-reversed");
const offsetTop = this.$()[0].getBoundingClientRect().top;
const windowHeight = $(window).height();
const headerHeight = this.$(".select-box-header").outerHeight();
const filterHeight = this.$(".select-box-filter").outerHeight();
if (windowHeight - (offsetTop + this.get("maxCollectionHeight") + filterHeight + headerHeight) < 0) {
this.$().addClass("is-reversed");
this.$(".select-box-body").css({
left: this.get("horizontalOffset"),
top: "",
bottom: headerHeight + this.get("verticalOffset")
});
} else {
this.$(".select-box-body").css({
left: this.get("horizontalOffset"),
top: headerHeight + this.get("verticalOffset"),
bottom: ""
});
_castInteger(id) {
if (this.get("castInteger") === true && Ember.isPresent(id)) {
return parseInt(id, 10);
}
return id;
},
_applyFixedPosition(width, height) {
const $placeholder = $(`<div class='select-box-fixed-placeholder-${this.get("componentId")}' style='vertical-align: middle; height: ${height}px; width: ${width}px; line-height: ${height}px;display:inline-block'></div>`);
this.$()
.before($placeholder)
.css({
width,
position: "fixed",
"margin-top": -this.get("scrollableParent").scrollTop(),
"margin-left": -width
});
this.get("scrollableParent").on("scroll.select-box", () => this.set("expanded", false) );
},
_removeFixedPosition() {
$(`.select-box-fixed-placeholder-${this.get("componentId")}`).remove();
this.$().css({
top: "auto",
left: "auto",
"margin-left": "auto",
"margin-top": "auto",
position: "relative"
});
this.get("scrollableParent").off("scroll.select-box");
}
});

View File

@ -1,3 +1,9 @@
export default Ember.Component.extend({
classNames: "select-box-collection"
classNames: "select-box-collection",
actions: {
onClearSelection() {
this.sendAction("onClearSelection");
}
}
});

View File

@ -1,3 +1,5 @@
export default Ember.Component.extend({
classNames: "select-box-filter"
classNames: "select-box-filter",
classNameBindings: ["focused:is-focused"]
});

View File

@ -1,34 +1,36 @@
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
layoutName: "components/select-box/select-box-row",
classNames: "select-box-row",
tagName: "li",
classNameBindings: ["isHighlighted"],
attributeBindings: ["title"],
attributeBindings: ["text:title"],
classNameBindings: ["isHighlighted:is-highlighted"],
lastHoveredId: null,
@computed("titleForRow")
title(titleForRow) {
return titleForRow(this);
},
@computed("templateForRow")
template(templateForRow) {
return templateForRow(this);
},
@computed("shouldHighlightRow", "lastHovered", "value")
isHighlighted(shouldHighlightRow) {
return shouldHighlightRow(this);
},
mouseEnter() {
this.sendAction("onHover", this.get("content.id"));
this.sendAction("onHover", this.get("content"));
},
click() {
this.sendAction("onSelect", this.get("content.id"));
},
didReceiveAttrs() {
this._super();
this.set("isHighlighted", this._isHighlighted());
this.set("text", this.get("content.text"));
},
_isHighlighted() {
if(_.isUndefined(this.get("lastHoveredId"))) {
return this.get("content.id") === this.get("selectedId");
} else {
return this.get("content.id") === this.get("lastHoveredId");
}
},
this.sendAction("onSelect", this.get("content"));
}
});

View File

@ -1,44 +0,0 @@
import { autoUpdatingRelativeAge } from 'discourse/lib/formatter';
import { userPath } from 'discourse/lib/url';
export function actionDescriptionHtml(actionCode, createdAt, username) {
const dt = new Date(createdAt);
const when = autoUpdatingRelativeAge(dt, { format: 'medium-with-ago' });
var who = "";
if (username) {
if (actionCode === "invited_group" || actionCode === "removed_group") {
who = `<a class="mention-group" href="/groups/${username}">@${username}</a>`;
} else {
who = `<a class="mention" href="${userPath(username)}">@${username}</a>`;
}
}
return I18n.t(`action_codes.${actionCode}`, { who, when }).htmlSafe();
}
export function actionDescription(actionCode, createdAt, username) {
return function() {
const ac = this.get(actionCode);
if (ac) {
return actionDescriptionHtml(ac, this.get(createdAt), this.get(username));
}
}.property(actionCode, createdAt);
}
export default Ember.Component.extend({
layoutName: 'components/small-action', // needed because `time-gap` inherits from this
classNames: ['small-action'],
description: actionDescription('actionCode', 'post.created_at', 'post.action_code_who'),
actions: {
edit() {
this.sendAction('editPost', this.get('post'));
},
delete() {
this.sendAction('deletePost', this.get('post'));
}
}
});

View File

@ -0,0 +1,53 @@
import computed from 'ember-addons/ember-computed-decorators';
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
import { iconHTML } from 'discourse-common/lib/icon-library';
export default Ember.Component.extend({
elementId: 'suggested-topics',
@computed('topic')
suggestedTitle(topic) {
return topic.get('isPrivateMessage') ?
`<a href="${this.get('pmPath')}">${iconHTML('envelope', { class: 'private-message-glyph' })}</a> ${I18n.t("suggested_topics.pm_title")}` :
I18n.t("suggested_topics.title");
},
@computed('topic', 'topicTrackingState.messageCount')
browseMoreMessage(topic) {
// TODO decide what to show for pms
if (topic.get('isPrivateMessage')) { return; }
const opts = { latestLink: `<a href="${Discourse.getURL("/latest")}">${I18n.t("topic.view_latest_topics")}</a>` };
let category = topic.get('category');
if (category && Em.get(category, 'id') === Discourse.Site.currentProp("uncategorized_category_id")) {
category = null;
}
if (category) {
opts.catLink = categoryBadgeHTML(category);
} else {
opts.catLink = "<a href=\"" + Discourse.getURL("/categories") + "\">" + I18n.t("topic.browse_all_categories") + "</a>";
}
const unreadTopics = this.topicTrackingState.countUnread();
const newTopics = this.topicTrackingState.countNew();
if (newTopics + unreadTopics > 0) {
const hasBoth = unreadTopics > 0 && newTopics > 0;
return I18n.messageFormat("topic.read_more_MF", {
"BOTH": hasBoth,
"UNREAD": unreadTopics,
"NEW": newTopics,
"CATEGORY": category ? true : false,
latestLink: opts.latestLink,
catLink: opts.catLink
});
} else if (category) {
return I18n.t("topic.read_more_in_category", opts);
} else {
return I18n.t("topic.read_more", opts);
}
},
});

View File

@ -1,13 +1,18 @@
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"),
dynamicHeaderText: false,
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();
},

View File

@ -46,6 +46,11 @@ export default Ember.Component.extend(CleansUp, {
return !this.siteSettings.prioritize_username_in_ux && name && name.trim().length > 0;
},
@computed('username', 'topicPostCount')
togglePostsLabel(username, count) {
return I18n.t("topic.filter_to", { username, count });
},
@computed('user.user_fields.@each.value')
publicUserFields() {
const siteUserFields = this.site.get('user_fields');

View File

@ -42,8 +42,11 @@ export default TextField.extend({
updateData: (opts && opts.updateData) ? opts.updateData : false,
dataSource: function(term) {
const termRegex = Discourse.User.currentProp('can_send_private_email_messages') ?
/[^a-zA-Z0-9_\-\.@\+]/ : /[^a-zA-Z0-9_\-\.]/;
var results = userSearch({
term: term.replace(/[^a-zA-Z0-9_\-\.]/, ''),
term: term.replace(termRegex, ''),
topicId: self.get('topicId'),
exclude: excludedUsernames(),
includeGroups,

View File

@ -1,8 +1,15 @@
import { propertyEqual } from 'discourse/lib/computed';
import { actionDescription } from "discourse/components/small-action";
import { actionDescription } from "discourse/widgets/post-small-action";
export default Ember.Component.extend({
classNameBindings: [":item", "item.hidden", "item.deleted:deleted", "moderatorAction"],
classNameBindings: [
":user-stream-item",
":item", // DEPRECATED: 'item' class
"item.hidden",
"item.deleted:deleted",
"moderatorAction"
],
moderatorAction: propertyEqual("item.post_type", "site.post_types.moderator_action"),
actionDescription: actionDescription("item.action_code", "item.created_at", "item.username"),
});

View File

@ -1,11 +1,17 @@
import UserBadge from 'discourse/models/user-badge';
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
import BadgeSelectController from "discourse/mixins/badge-select-controller";
export default Ember.Controller.extend({
export default Ember.Controller.extend(BadgeSelectController, {
queryParams: ['username'],
noMoreBadges: false,
userBadges: null,
application: Ember.inject.controller(),
hiddenSetTitle: true,
filteredList: function() {
return this.get('userBadgesAll').filterBy('badge.allow_title', true);
}.property('userBadgesAll'),
@computed('username')
user(username) {
@ -19,6 +25,11 @@ export default Ember.Controller.extend({
return username ? userCount : modelCount;
},
@computed("model.has_title_badges")
canSelectTitle(hasTitleBadges) {
return this.siteSettings.enable_badges && hasTitleBadges;
},
actions: {
loadMore() {
if (this.get('loadingMore')) {
@ -39,6 +50,10 @@ export default Ember.Controller.extend({
}).finally(()=>{
this.set('loadingMore', false);
});
},
toggleSetUserTitle() {
return this.toggleProperty('hiddenSetTitle');
}
},

View File

@ -38,6 +38,11 @@ export default Ember.Controller.extend(PasswordValidation, UsernameValidation, N
return usernameFailed || passwordFailed || nameFailed || userFieldsFailed;
},
@computed
fullnameRequired() {
return this.siteSettings.full_name_required || this.siteSettings.enable_names;
},
actions: {
submit() {

View File

@ -90,7 +90,6 @@ export default Ember.Controller.extend(ModalFunctionality, {
// Trigger the browser's password manager using the hidden static login form:
const $hidden_login_form = $('#hidden-login-form');
const destinationUrl = $.cookie('destination_url');
const shouldRedirectToUrl = self.session.get("shouldRedirectToUrl");
const ssoDestinationUrl = $.cookie('sso_destination_url');
$hidden_login_form.find('input[name=username]').val(self.get('loginName'));
$hidden_login_form.find('input[name=password]').val(self.get('loginPassword'));
@ -103,9 +102,6 @@ export default Ember.Controller.extend(ModalFunctionality, {
// redirect client to the original URL
$.cookie('destination_url', null);
$hidden_login_form.find('input[name=redirect]').val(destinationUrl);
} else if (shouldRedirectToUrl) {
self.session.set("shouldRedirectToUrl", null);
$hidden_login_form.find('input[name=redirect]').val(shouldRedirectToUrl);
} else {
$hidden_login_form.find('input[name=redirect]').val(window.location.href);
}
@ -222,14 +218,10 @@ export default Ember.Controller.extend(ModalFunctionality, {
// Reload the page if we're authenticated
if (options.authenticated) {
const destinationUrl = $.cookie('destination_url');
const shouldRedirectToUrl = self.session.get("shouldRedirectToUrl");
if (self.get('loginRequired') && destinationUrl) {
if (destinationUrl) {
// redirect client to the original URL
$.cookie('destination_url', null);
window.location.href = destinationUrl;
} else if (shouldRedirectToUrl) {
self.session.set("shouldRedirectToUrl", null);
window.location.href = shouldRedirectToUrl;
} else if (window.location.pathname === Discourse.getURL('/login')) {
window.location.pathname = Discourse.getURL('/');
} else {

View File

@ -1,4 +1,3 @@
import { iconHTML } from 'discourse-common/lib/icon-library';
import BufferedContent from 'discourse/mixins/buffered-content';
import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
import { spinnerHTML } from 'discourse/helpers/loading-spinner';
@ -8,7 +7,6 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
import computed from 'ember-addons/ember-computed-decorators';
import Composer from 'discourse/models/composer';
import DiscourseURL from 'discourse/lib/url';
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
import Post from 'discourse/models/post';
import debounce from 'discourse/lib/debounce';
import isElementInViewport from "discourse/lib/is-element-in-viewport";
@ -66,58 +64,12 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
return this.capabilities.isAndroid && loading;
},
@computed('model', 'topicTrackingState.messageCount')
browseMoreMessage(model) {
// TODO decide what to show for pms
if (model.get('isPrivateMessage')) { return; }
const opts = { latestLink: `<a href="${Discourse.getURL("/latest")}">${I18n.t("topic.view_latest_topics")}</a>` };
let category = model.get('category');
if (category && Em.get(category, 'id') === Discourse.Site.currentProp("uncategorized_category_id")) {
category = null;
}
if (category) {
opts.catLink = categoryBadgeHTML(category);
} else {
opts.catLink = "<a href=\"" + Discourse.getURL("/categories") + "\">" + I18n.t("topic.browse_all_categories") + "</a>";
}
const unreadTopics = this.topicTrackingState.countUnread();
const newTopics = this.topicTrackingState.countNew();
if (newTopics + unreadTopics > 0) {
const hasBoth = unreadTopics > 0 && newTopics > 0;
return I18n.messageFormat("topic.read_more_MF", {
"BOTH": hasBoth,
"UNREAD": unreadTopics,
"NEW": newTopics,
"CATEGORY": category ? true : false,
latestLink: opts.latestLink,
catLink: opts.catLink
});
} else if (category) {
return I18n.t("topic.read_more_in_category", opts);
} else {
return I18n.t("topic.read_more", opts);
}
},
@computed('model')
pmPath(model) {
return this.currentUser && this.currentUser.pmPath(model);
},
@computed('model')
suggestedTitle(model) {
return model.get('isPrivateMessage') ?
`<a href="${this.get('pmPath')}">${iconHTML('envelope', { class: 'private-message-glyph' })}</a> ${I18n.t("suggested_topics.pm_title")}` :
I18n.t("suggested_topics.title");
},
init() {
this._super();
this.set('selectedPosts', []);

View File

@ -165,7 +165,7 @@ export default function(options) {
};
if (isInput) {
const width = this.width();
const width = Math.max(this.width(), 200);
if (options.updateData) {
wrap = this.parent();

View File

@ -33,6 +33,7 @@ function positioningWorkaround($fixedElement) {
}
const fixedElement = $fixedElement[0];
const oldHeight = fixedElement.style.height;
var done = false;
var originalScrollTop = 0;
@ -46,7 +47,11 @@ function positioningWorkaround($fixedElement) {
fixedElement.style.position = '';
fixedElement.style.top = '';
fixedElement.style.height = '';
fixedElement.style.height = oldHeight;
setTimeout(()=>{
$(fixedElement).removeClass('no-transition');
},500);
$(window).scrollTop(originalScrollTop);
@ -63,10 +68,6 @@ function positioningWorkaround($fixedElement) {
return;
}
if (composingTopic) {
return false;
}
positioningWorkaround.blur(evt);
};
@ -97,17 +98,22 @@ function positioningWorkaround($fixedElement) {
$(window).scrollTop(0);
let i = 20;
let interval = setInterval(()=>{
$(window).scrollTop(0);
if (i--===0) {
clearInterval(interval);
}
}, 10);
fixedElement.style.top = '0px';
composingTopic = $('#reply-control select.category-combobox').length > 0;
composingTopic = $('#reply-control .category-select-box').length > 0;
const height = calcHeight(composingTopic);
fixedElement.style.height = height + "px";
// I used to do this, but it seems like we don't need to with position
// fixed
// setTimeout(()=>$(window).scrollTop(0),500);
$(fixedElement).addClass('no-transition');
evt.preventDefault();
self.focus();

View File

@ -18,6 +18,7 @@ export function translateResults(results, opts) {
if (!results.users) { results.users = []; }
if (!results.posts) { results.posts = []; }
if (!results.categories) { results.categories = []; }
if (!results.tags) { results.tags = []; }
const topicMap = {};
results.topics = results.topics.map(function(topic){
@ -44,12 +45,17 @@ export function translateResults(results, opts) {
return Category.list().findBy('id', category.id);
}).compact();
results.tags = results.tags.map(function(tag){
let tagName = Handlebars.Utils.escapeExpression(tag.name);
return Ember.Object.create({ id: tagName, url: Discourse.getURL("/tags/" + tagName) });
}).compact();
const r = results.grouped_search_result;
results.resultTypes = [];
// TODO: consider refactoring front end to take a better structure
if (r) {
[['topic','posts'],['user','users'],['category','categories']].forEach(function(pair){
[['topic','posts'],['user','users'],['category','categories'],['tag','tags']].forEach(function(pair){
const type = pair[0], name = pair[1];
if (results[name].length > 0) {
var result = {

View File

@ -46,6 +46,7 @@ function organizeResults(r, options) {
var exclude = options.exclude || [],
limit = options.limit || 5,
users = [],
emails = [],
groups = [],
results = [];
@ -59,6 +60,12 @@ function organizeResults(r, options) {
});
}
if (options.term.match(/@/)) {
let e = { username: options.term };
emails = [ e ];
results.push(e);
}
if (r.groups) {
r.groups.every(function(g) {
if (results.length > limit && options.term.toLowerCase() !== g.name.toLowerCase()) return false;
@ -71,6 +78,7 @@ function organizeResults(r, options) {
}
results.users = users;
results.emails = emails;
results.groups = groups;
return results;
}
@ -94,7 +102,7 @@ export default function userSearch(options) {
return new Ember.RSVP.Promise(function(resolve) {
// TODO site setting for allowed regex in username
if (term.match(/[^\w\.\-]/)) {
if (term.match(/[^\w_\-\.@\+]/)) {
resolve([]);
return;
}

View File

@ -298,7 +298,7 @@ export function getUploadMarkdown(upload) {
if (isAnImage(upload.original_filename)) {
const split = upload.original_filename.split('.');
const name = split[split.length-2];
return `![${name}|${upload.width}x${upload.height}](${upload.url})`;
return `![${name}|${upload.width}x${upload.height}](${upload.short_url || upload.url})`;
} else if (!Discourse.SiteSettings.prevent_anons_from_downloading_files && (/\.(mov|mp4|webm|ogv|mp3|ogg|wav|m4a)$/i).test(upload.original_filename)) {
return uploadLocation(upload.url);
} else {

View File

@ -108,9 +108,9 @@ const Group = RestModel.extend({
return this.get('flair_color') ? this.get('flair_color').replace(new RegExp("[^0-9a-fA-F]", "g"), "") : null;
},
@computed('alias_level')
canEveryoneMention(aliasLevel) {
return aliasLevel === '99';
@computed('mentionable_level')
canEveryoneMention(mentionableLevel) {
return mentionableLevel === '99';
},
@observes("visibility_level", "canEveryoneMention")
@ -131,7 +131,8 @@ const Group = RestModel.extend({
asJSON() {
const attrs = {
name: this.get('name'),
alias_level: this.get('alias_level'),
mentionable_level: this.get('mentionable_level'),
messageable_level: this.get('messageable_level'),
visibility_level: this.get('visibility_level'),
automatic_membership_email_domains: this.get('emailDomains'),
automatic_membership_retroactive: !!this.get('automatic_membership_retroactive'),

View File

@ -86,6 +86,9 @@ UserBadge.reopenClass({
@returns {Promise} a promise that resolves to an array of `UserBadge`.
**/
findByUsername: function(username, options) {
if (!username) {
return Em.RSVP.resolve([]);
}
var url = "/user-badges/" + username + ".json";
if (options && options.grouped) {
url += "?grouped=true";

View File

@ -121,11 +121,11 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
user clicks "No", reopenModal. If user clicks "Yes", be sure to call closeModal.
**/
hideModal() {
$('#discourse-modal').modal('hide');
$('.d-modal.fixed-modal').modal('hide');
},
reopenModal() {
$('#discourse-modal').modal('show');
$('.d-modal.fixed-modal').modal('show');
},
editCategory(category) {

View File

@ -30,9 +30,20 @@ export default Discourse.Route.extend({
afterModel(model, transition) {
const username = transition.queryParams && transition.queryParams.username;
return UserBadge.findByBadgeId(model.get("id"), {username}).then(userBadges => {
this.userBadges = userBadges;
const userBadgesGrant = UserBadge.findByBadgeId(model.get("id"), {username}).then(userBadges => {
this.userBadgesGrant = userBadges;
});
const userBadgesAll = UserBadge.findByUsername(username).then(userBadges => {
this.userBadgesAll = userBadges;
});
const promises = {
userBadgesGrant,
userBadgesAll,
};
return Ember.RSVP.hash(promises);
},
titleToken() {
@ -42,8 +53,10 @@ export default Discourse.Route.extend({
}
},
setupController(controller, model) {
controller.set("model", model);
controller.set("userBadges", this.userBadges);
controller.set("userBadges", this.userBadgesGrant);
controller.set("userBadgesAll", this.userBadgesAll);
}
});

View File

@ -21,8 +21,8 @@ export default Discourse.Route.extend({
});
} else if (params.groupname) {
// send a message to a group
Group.mentionable(params.groupname).then(result => {
if (result.mentionable) {
Group.messageable(params.groupname).then(result => {
if (result.messageable) {
Ember.run.next(() => e.send("createNewMessageViaParams", params.groupname, params.title, params.body));
} else {
bootbox.alert(I18n.t("composer.cant_send_pm", { username: params.groupname }));
@ -33,7 +33,7 @@ export default Discourse.Route.extend({
}
});
} else {
this.session.set("shouldRedirectToUrl", window.location.href);
$.cookie('destination_url', window.location.href);
this.replaceWith('login');
}
}

View File

@ -13,7 +13,7 @@ export default Discourse.Route.extend({
});
} else {
// User is not logged in
self.session.set("shouldRedirectToUrl", window.location.href);
$.cookie('destination_url', window.location.href);
self.replaceWith('login');
}
}

View File

@ -2,5 +2,6 @@ import UserActivityStreamRoute from "discourse/routes/user-activity-stream";
import UserAction from "discourse/models/user-action";
export default UserActivityStreamRoute.extend({
userActionType: UserAction.TYPES["posts"]
userActionType: UserAction.TYPES["posts"],
noContentHelpKey: 'user_activity.no_replies'
});

View File

@ -7,11 +7,19 @@
<div class='show-badge-details'>
{{badge-card badge=model size="large" count=userBadges.grant_count}}
<div class='badge-grant-info'>
<div class='badge-grant-info {{if hiddenSetTitle '' 'hidden'}}'>
<div>
{{#if model.allow_title}}
<div class='grant-info-item'>
{{i18n 'badges.allow_title'}}
{{#if userBadges}}
{{#if model.allow_title}}
{{d-button
class='btn btn-small pad-left no-text'
action='toggleSetUserTitle'
icon='pencil'}}
{{/if}}
{{/if}}
</div>
{{/if}}
{{#if model.multiple_grant}}
@ -21,6 +29,10 @@
{{/if}}
</div>
</div>
<div class='badge-set-title {{if hiddenSetTitle 'hidden' ''}}'>
{{badge-title selectableUserBadges=selectableUserBadges user=user}}
<button class='btn btn-default close-btn' {{action "toggleSetUserTitle"}}>{{i18n 'close'}}</button>
</div>
</div>
{{#if userBadges}}

View File

@ -0,0 +1,25 @@
<section class='user-content'>
<form class="form-horizontal">
<div class="control-group">
<div class="controls">
<h3>{{i18n 'badges.select_badge_for_title'}}</h3>
</div>
</div>
<div class="control-group">
<label class="control-label"></label>
<div class="controls">
{{combo-box valueAttribute="id" value=selectedUserBadgeId nameProperty="badge.name" content=selectableUserBadges}}
</div>
</div>
<div class="control-group">
<div class="controls">
<button class="btn btn-primary" disabled={{disableSave}} {{action "save"}}> {{savingStatus}} </button>
{{#if saved}}{{i18n 'saved'}}{{/if}}
</div>
</div>
</form>
</section>

View File

@ -0,0 +1,24 @@
<div class="modal-outer-container">
<div class="modal-middle-container">
<div class="modal-inner-container">
<div class="modal-header">
<div class='modal-close'>
<a class="close" {{action closeModal}}>{{d-icon "times"}}</a>
</div>
<h3>{{title}}</h3>
</div>
<div id='modal-alert'></div>
{{yield}}
{{#each errors as |error|}}
<div class="alert alert-error">
<button class="close" data-dismiss="alert">×</button>
{{error}}
</div>
{{/each}}
</div>
</div>
</div>

View File

@ -19,7 +19,11 @@
{{/each}}
{{else}}
<label>{{i18n 'category.parent'}}</label>
{{category-chooser valueAttribute="id" value=category.parent_category_id categories=parentCategories rootNone=true allowUncategorized="true"}}
{{category-select-box
clearSelectionLabel="category.none"
value=category.parent_category_id
categories=parentCategories
allowUncategorized=true}}
{{/if}}
</section>
{{/if}}

View File

@ -1,4 +1,4 @@
<div class='item'>
<div class="user-stream-item item">{{!-- DEPRECATED: 'item' class --}}
<div class='clearfix info'>
<a href="{{unbound post.user.userUrl}}" data-user-card="{{unbound post.user.username}}" class='avatar-link'><div class='avatar-wrapper'>{{avatar post.user imageSize="large" extraClasses="actor" ignoreTitle="true"}}</div></a>
<span class='time'>{{format-date post.created_at leaveAgo="true"}}</span>

View File

@ -7,6 +7,7 @@
<div class="location-box">
<a class="close pull-right" {{action "hide"}}>{{d-icon "times"}}</a>
<h4>{{i18n 'ip_lookup.title'}}</h4>
<p class='powered-by'>{{{i18n 'ip_lookup.powered_by'}}}</p>
<dl>
{{#if location}}
{{#if location.hostname}}

View File

@ -24,7 +24,7 @@
<span class="edit-title">
{{text-field value=buffered.title maxlength=siteSettings.max_topic_title_length}}
</span>
{{category-chooser value=buffered.category_id}}
{{category-select-box value=buffered.category_id}}
{{else}}
<span class='post-title'>
{{i18n "queue.topic"}}

View File

@ -4,16 +4,18 @@
aria-haspopup="true"
role="button"
aria-labelledby="select-box-input-{{componentId}}"
tabindex={{tabindex}}
/>
{{component selectBoxHeaderComponent
text=headerText
text=generatedHeadertext
focused=focused
caretUpIcon=caretUpIcon
caretDownIcon=caretDownIcon
onToggle=(action "onToggle")
icon=icon
expanded=expanded
value=value
}}
<div class="select-box-body">
@ -21,21 +23,29 @@
{{#if filterable}}
{{component selectBoxFilterComponent
onFilterChange=(action "onFilterChange")
filterIcon=filterIcon
filterPlaceholder=filterPlaceholder
icon=filterIcon
focused=filterFocused
placeholder=filterPlaceholder
}}
{{/if}}
{{component selectBoxCollectionComponent
clearSelectionLabel=clearSelectionLabel
filteredContent=filteredContent
selectBoxRowComponent=selectBoxRowComponent
lastHoveredId=lastHoveredId
templateForRow=templateForRow
shouldHighlightRow=shouldHighlightRow
titleForRow=titleForRow
lastHovered=lastHovered
onSelectRow=(action "onSelectRow")
onHoverRow=(action "onHoverRow")
noContentText=noContentText
selectedId=value
onClearSelection=(action "onClearSelection")
noContentLabel=noContentLabel
value=value
}}
{{/if}}
</div>
<div class="select-box-wrapper"></div>
{{#if wrapper}}
<div class="select-box-wrapper"></div>
{{/if}}

View File

@ -1,16 +1,25 @@
<ul class="collection">
{{#if clearSelectionLabel}}
<li {{action "onClearSelection" on="click"}} class="select-box-row clear-selection">
{{i18n clearSelectionLabel}}
</li>
{{/if}}
{{#each filteredContent as |content|}}
{{component selectBoxRowComponent
content=content
lastHoveredId=lastHoveredId
templateForRow=templateForRow
titleForRow=titleForRow
shouldHighlightRow=shouldHighlightRow
lastHovered=lastHovered
onSelect=onSelectRow
onHover=onHoverRow
selectedId=selectedId
value=value
}}
{{else}}
{{#if noContentText}}
{{#if noContentLabel}}
<li class="select-box-row no-content">
{{noContentText}}
{{noContentLabel}}
</li>
{{/if}}
{{/each}}

View File

@ -1,14 +1,14 @@
<div class="wrapper">
{{input
tabindex="-1"
class="filter-query"
placeholder=filterPlaceholder
key-up=onFilterChange
}}
{{input
tabindex="-1"
class="filter-query"
placeholder=placeholder
key-up=onFilterChange
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck=false
}}
{{#if filterIcon}}
<div class="filter-icon">
{{d-icon filterIcon}}
</div>
{{/if}}
</div>
{{#if icon}}
{{d-icon icon class="filter-icon"}}
{{/if}}

View File

@ -1,15 +1,9 @@
<div class="wrapper">
{{#if icon}}
<div class="icon">
{{d-icon icon}}
</div>
{{/if}}
{{#if icon}}
{{d-icon icon class="icon"}}
{{/if}}
<span class="current-selection">
{{text}}
</span>
<span class="current-selection">
{{text}}
</span>
<div class="caret-icon">
{{d-icon caretIcon}}
</div>
</div>
{{d-icon caretIcon class="caret-icon"}}

View File

@ -1,7 +1 @@
{{#if content.icon}}
{{d-icon content.icon}}
{{/if}}
<p class="text">
{{content.text}}
</p>
{{{template}}}

View File

@ -1,13 +0,0 @@
<div class='small-action-desc'>
{{#if post}}
{{#if post.can_delete}}
<button {{action "delete"}} title={{i18n "post.controls.delete"}}>{{d-icon "times"}}</button>
{{/if}}
{{#if post.can_edit}}
<button {{action "edit"}} title={{i18n "post.controls.edit"}}>{{d-icon "pencil"}}</button>
{{/if}}
<a href={{post.usernameUrl}} data-user-card={{post.username}}>
{{avatar post imageSize="small"}}
</a>
{{/if}}
</div>

View File

@ -0,0 +1,12 @@
<h3>{{{suggestedTitle}}}</h3>
<div class="topics">
{{#if topic.isPrivateMessage}}
{{basic-topic-list
hideCategory="true"
showPosters="true"
topics=topic.details.suggested_topics}}
{{else}}
{{basic-topic-list topics=topic.details.suggested_topics}}
{{/if}}
</div>
<h3 class='suggested-topics-message'>{{{browseMoreMessage}}}</h3>

View File

@ -10,6 +10,7 @@
flairColor=user.primary_group_flair_color
groupName=user.primary_group_name}}
{{/if}}
{{plugin-outlet name="user-card-avatar-flair" args=(hash user=user) tagName='div'}}
</div>
<div class="names">
@ -36,19 +37,41 @@
<ul class="usercard-controls">
{{#if user.can_send_private_message_to_user}}
<li><a class='btn btn-primary' {{action "composePrivateMessage" user post}}>{{d-icon "envelope"}}{{i18n 'user.private_message'}}</a></li>
<li>
{{d-button
class="btn-primary"
action=(action "composePrivateMessage" user post)
icon="envelope"
label="user.private_message"}}
</li>
{{/if}}
{{#if showFilter}}
<li><a class='btn' href {{action "togglePosts" user}}>{{d-icon "filter"}}{{i18n 'topic.filter_to' username=username count=topicPostCount}}</a></li>
<li>
{{d-button
action=(action "togglePosts" user)
icon="filter"
translatedLabel=togglePostsLabel}}
</li>
{{/if}}
{{#if hasUserFilters}}
<li><a class='btn' href {{action "cancelFilter"}}>{{d-icon "times"}}{{i18n 'topic.filters.cancel'}}</a></li>
<li>
{{d-button
action="cancelFilter"
icon="times"
label="topic.filters.cancel"}}
</li>
{{/if}}
{{#if showDelete}}
<li><a class='btn btn-danger' href {{action "deleteUser" user}}>{{d-icon "exclamation-triangle"}}{{i18n 'admin.user.delete'}}</a></li>
<li>
{{d-button
class="btn-danger"
action=(action "deleteUser" user)
icon="exclamation-triangle"
label="admin.user.delete"}}
</li>
{{/if}}
</ul>

View File

@ -26,7 +26,7 @@
<p class='excerpt' data-topic-id="{{item.topic_id}}" data-post-id="{{item.post_id}}" data-user-id="{{item.user_id}}">{{{item.excerpt}}}</p>
{{#each item.children as |child|}}
<div class='child-actions'>
<div class='user-stream-item-actions child-actions'>{{!-- DEPRECATED: 'child-actions' class --}}
{{d-icon child.icon class="icon"}}
{{#each child.items as |grandChild|}}
{{#if grandChild.removableBookmark}}

View File

@ -1,3 +1,3 @@
{{#each stream.content as |item|}}
{{stream-item item=item removeBookmark=(action "removeBookmark")}}
{{user-stream-item item=item removeBookmark=(action "removeBookmark")}}
{{/each}}

View File

@ -72,7 +72,7 @@
{{#if model.showCategoryChooser}}
<div class="category-input">
{{category-chooser valueAttribute="id" value=model.categoryId scopedCategoryId=scopedCategoryId tabindex="3"}}
{{category-select-box value=model.categoryId scopedCategoryId=scopedCategoryId tabindex="3"}}
{{popup-input-tip validation=categoryValidation}}
</div>
{{#if model.archetype.hasOptions}}

View File

@ -1,4 +1,4 @@
{{#load-more selector=".user-stream .item" action=(action "loadMore")}}
{{#load-more selector=".user-stream .user-stream-item" action=(action "loadMore")}}
<div class='user-stream'>
{{#each model as |post|}}
{{group-post post=post}}

View File

@ -30,11 +30,13 @@
<div class="instructions">{{i18n 'user.username.instructions'}}</div>
</div>
<div class="input name-input">
<label>{{i18n 'invites.name_label'}}</label>
{{input value=accountName id="new-account-name" name="name"}}
<div class="instructions">{{nameInstructions}}</div>
</div>
{{#if fullnameRequired}}
<div class="input name-input">
<label>{{i18n 'invites.name_label'}}</label>
{{input value=accountName id="new-account-name" name="name"}}
<div class="instructions">{{nameInstructions}}</div>
</div>
{{/if}}
<div class="input password-input">
<label>{{i18n 'invites.password_label'}}</label>

View File

@ -1,4 +1,6 @@
<{{view.tagName}} class='num posts-map posts {{view.likesHeat}}' title='{{view.title}}'>
{{raw-plugin-outlet name="topic-list-before-reply-count"}}
<a href class='posts-map badge-posts {{view.likesHeat}}'>{{number topic.replyCount noTitle="true"}}</a>
<a href class='posts-map badge-posts {{view.likesHeat}}'>
{{raw-plugin-outlet name="topic-list-before-reply-count"}}
{{number topic.replyCount noTitle="true"}}
</a>
</{{view.tagName}}>

View File

@ -1,24 +1,10 @@
{{#d-modal modalClass=modalClass title=title class="hidden"}}
<div class="modal-outer-container">
<div class="modal-middle-container">
<div class="modal-inner-container">
<div class="modal-header">
<div class='modal-close'>
<a class="close" {{action "closeModal"}}>{{d-icon "times"}}</a>
</div>
{{#d-modal
modalClass=modalClass
title=title
class="hidden"
errors=errors
closeModal=(route-action "closeModal")}}
<h3>{{title}}</h3>
</div>
{{outlet "modalBody"}}
<div id='modal-alert'></div>
{{outlet "modalBody"}}
{{#each errors as |error|}}
<div class="alert alert-error">
<button class="close" data-dismiss="alert">×</button>
{{error}}
</div>
{{/each}}
</div>
</div>
</div>
{{/d-modal}}

View File

@ -1,6 +1,6 @@
<p>{{i18n "topics.bulk.choose_new_category"}}</p>
<p>{{category-chooser value=newCategoryId}}</p>
<p>{{category-select-box value=newCategoryId}}</p>
{{#conditional-loading-spinner condition=loading}}
{{d-button action="changeCategory" label="topics.bulk.change_category"}}

View File

@ -13,10 +13,9 @@
{{else if publishToCategory}}
<div class="control-group">
<label>{{i18n 'topic.topic_status_update.publish_to'}}</label>
{{category-chooser
valueAttribute="id"
value=topicTimer.category_id
excludeCategoryId=excludeCategoryId}}
{{category-select-box
value=topicTimer.category_id
excludeCategoryId=excludeCategoryId}}
</div>
{{auto-update-input

View File

@ -6,7 +6,7 @@
{{text-field value=topicName placeholderKey="composer.title_placeholder" elementId='split-topic-name'}}
<label>{{i18n 'categories.category'}}</label>
{{category-chooser value=categoryId}}
{{category-select-box value=categoryId class="small"}}
</form>
{{/d-modal-body}}

View File

@ -1,16 +1,48 @@
{{#d-section pageClass="user-preferences" class="user-navigation"}}
{{#mobile-nav class='preferences-nav' desktopClass='preferences-list action-list nav-stacked' currentPath=application.currentPath}}
<li class='no-glyph nav-account'>{{#link-to 'preferences.account'}}{{i18n 'user.preferences_nav.account'}}{{/link-to}}</li>
<li class='no-glyph nav-profile'>{{#link-to 'preferences.profile'}}{{i18n 'user.preferences_nav.profile'}}{{/link-to}}</li>
<li class='no-glyph nav-emails'>{{#link-to 'preferences.emails'}}{{i18n 'user.preferences_nav.emails'}}{{/link-to}}</li>
<li class='no-glyph nav-notifications'>{{#link-to 'preferences.notifications'}}{{i18n 'user.preferences_nav.notifications'}}{{/link-to}}</li>
<li class='no-glyph indent nav-categories'>{{#link-to 'preferences.categories'}}{{i18n 'user.preferences_nav.categories'}}{{/link-to}}</li>
<li class='nav-account'>
{{#link-to 'preferences.account'}}
{{i18n 'user.preferences_nav.account'}}
{{/link-to}}
</li>
<li class='nav-profile'>
{{#link-to 'preferences.profile'}}
{{i18n 'user.preferences_nav.profile'}}
{{/link-to}}
</li>
<li class='nav-emails'>
{{#link-to 'preferences.emails'}}
{{i18n 'user.preferences_nav.emails'}}
{{/link-to}}
</li>
<li class='nav-notifications'>
{{#link-to 'preferences.notifications'}}
{{i18n 'user.preferences_nav.notifications'}}
{{/link-to}}
</li>
<li class='indent nav-categories'>
{{#link-to 'preferences.categories'}}
{{i18n 'user.preferences_nav.categories'}}
{{/link-to}}
</li>
{{#if siteSettings.tagging_enabled}}
<li class='no-glyph indent nav-tags'>{{#link-to 'preferences.tags'}}{{i18n 'user.preferences_nav.tags'}}{{/link-to}}</li>
<li class='indent nav-tags'>
{{#link-to 'preferences.tags'}}
{{i18n 'user.preferences_nav.tags'}}
{{/link-to}}
</li>
{{/if}}
<li class='no-glyph nav-interface'>{{#link-to 'preferences.interface'}}{{i18n 'user.preferences_nav.interface'}}{{/link-to}}</li>
<li class='nav-interface'>
{{#link-to 'preferences.interface'}}
{{i18n 'user.preferences_nav.interface'}}
{{/link-to}}
</li>
{{#if model.userApiKeys}}
<li class='no-glyph nav-apps'>{{#link-to 'preferences.apps'}}{{i18n 'user.preferences_nav.apps'}}{{/link-to}}</li>
<li class='nav-apps'>
{{#link-to 'preferences.apps'}}
{{i18n 'user.preferences_nav.apps'}}
{{/link-to}}
</li>
{{/if}}
{{plugin-outlet name="user-preferences-nav" connectorTagName="li" args=(hash model=model)}}
{{/mobile-nav}}

View File

@ -2,7 +2,7 @@
<label class="control-label">{{i18n 'user.email_settings'}}</label>
<div class='controls controls-dropdown'>
<label>{{i18n 'user.email_previous_replies.title'}}</label>
{{combo-box valueAttribute="value" content=previousRepliesOptions value=model.user_option.email_previous_replies}}
{{select-box idKey="value" textKey="name" content=previousRepliesOptions value=model.user_option.email_previous_replies}}
</div>
{{preference-checkbox labelKey="user.email_in_reply_to" checked=model.user_option.email_in_reply_to}}
{{preference-checkbox labelKey="user.email_private_messages" checked=model.user_option.email_private_messages}}
@ -25,7 +25,7 @@
{{preference-checkbox labelKey="user.email_digests.title" disabled=model.user_option.mailing_list_mode checked=model.user_option.email_digests}}
{{#if model.user_option.email_digests}}
<div class='controls controls-dropdown'>
{{combo-box valueAttribute="value" content=digestFrequencies value=model.user_option.digest_after_minutes}}
{{select-box idKey="value" filterable=true textKey="name" content=digestFrequencies value=model.user_option.digest_after_minutes}}
</div>
{{preference-checkbox labelKey="user.include_tl0_in_digests" disabled=model.user_option.mailing_list_mode checked=model.user_option.include_tl0_in_digests}}
{{/if}}

View File

@ -2,7 +2,7 @@
<div class="control-group theme">
<label class="control-label">{{i18n 'user.theme'}}</label>
<div class="controls">
{{combo-box content=userSelectableThemes value=themeKey}}
{{select-box textKey="name" content=userSelectableThemes value=themeKey}}
</div>
<div class="controls">
{{preference-checkbox labelKey="user.theme_default_on_all_devices" checked=makeThemeDefault}}

View File

@ -19,7 +19,7 @@
{{text-field id="edit-title" value=buffered.title maxlength=siteSettings.max_topic_title_length autofocus="true"}}
{{#if showCategoryChooser}}
<br>
{{category-chooser valueAttribute="id" value=buffered.category_id}}
{{category-select-box class="small" value=buffered.category_id}}
{{/if}}
{{#if canEditTags}}
@ -246,21 +246,8 @@
{{plugin-outlet name="topic-above-suggested" args=(hash model=model)}}
{{#if model.details.suggested_topics.length}}
<div id="suggested-topics">
<h3>{{{suggestedTitle}}}</h3>
<div class="topics">
{{#if model.isPrivateMessage}}
{{basic-topic-list hideCategory="true"
showPosters="true"
topics=model.details.suggested_topics}}
{{else}}
{{basic-topic-list topics=model.details.suggested_topics}}
{{/if}}
</div>
<h3>{{{browseMoreMessage}}}</h3>
</div>
{{suggested-topics topic=model}}
{{/if}}
{{/if}}
{{/conditional-loading-spinner}}

View File

@ -9,6 +9,18 @@
</a>
</li>
{{/each}}
{{#if options.emails}}
{{#each options.emails as |email|}}
<li>
<a href title="{{email.username}}">
<i class='fa fa-envelope'></i>
<span class='username'>{{email.username}}</span>
</a>
</li>
{{/each}}
{{/if}}
{{#if options.groups}}
{{#each options.groups as |group|}}
<li>

View File

@ -1,26 +1,20 @@
{{#d-section pageClass="user-activity" class="user-navigation" scrollTop="false"}}
{{#mobile-nav class='activity-nav' desktopClass='action-list activity-list nav-stacked' currentPath=currentPath}}
<li class='no-glyph'>
<li>
{{#link-to 'userActivity.index'}}{{i18n 'user.filters.all'}}{{/link-to}}
</li>
<li class='no-glyph'>
<li>
{{#link-to 'userActivity.topics'}}{{i18n 'user_action_groups.4'}}{{/link-to}}
</li>
<li>
{{#link-to 'userActivity.replies'}}
{{d-icon "reply" class="glyph"}}{{i18n 'user_action_groups.5'}}
{{/link-to}}
{{#link-to 'userActivity.replies'}}{{i18n 'user_action_groups.5'}}{{/link-to}}
</li>
<li>
{{#link-to 'userActivity.likesGiven'}}
{{d-icon "heart" class="glyph"}}{{i18n 'user_action_groups.1'}}
{{/link-to}}
{{#link-to 'userActivity.likesGiven'}}{{i18n 'user_action_groups.1'}}{{/link-to}}
</li>
{{#if showBookmarks}}
<li>
{{#link-to 'userActivity.bookmarks'}}
{{d-icon "bookmark" class="glyph"}}{{i18n 'user_action_groups.3'}}
{{/link-to}}
{{#link-to 'userActivity.bookmarks'}}{{i18n 'user_action_groups.3'}}{{/link-to}}
</li>
{{/if}}
{{plugin-outlet name="user-activity-bottom"

View File

@ -1,25 +1 @@
<section class='user-content'>
<form class="form-horizontal">
<div class="control-group">
<div class="controls">
<h3>{{i18n 'badges.select_badge_for_title'}}</h3>
</div>
</div>
<div class="control-group">
<label class="control-label"></label>
<div class="controls">
{{combo-box valueAttribute="id" value=selectedUserBadgeId nameProperty="badge.name" content=selectableUserBadges}}
</div>
</div>
<div class="control-group">
<div class="controls">
<button class="btn btn-primary" disabled={{disableSave}} {{action "save"}}>{{savingStatus}}</button>
{{#if saved}}{{i18n 'saved'}}{{/if}}
</div>
</div>
</form>
</section>
{{badge-title selectableUserBadges=selectableUserBadges user=user}}

View File

@ -1,21 +1,30 @@
{{#d-section pageClass="user-notifications" class="user-navigation"}}
{{#mobile-nav class='notifications-nav' desktopClass='notification-list action-list nav-stacked' currentPath=application.currentPath}}
<li class='no-glyph'>
{{#link-to 'userNotifications.index'}}{{i18n 'user.filters.all'}}{{/link-to}}
<li>
{{#link-to 'userNotifications.index'}}
{{i18n 'user.filters.all'}}
{{/link-to}}
</li>
<li>
{{#link-to 'userNotifications.responses'}}
{{d-icon "reply" class="glyph"}}
{{i18n 'user_action_groups.6'}}
{{/link-to}}
</li>
<li>
{{#link-to 'userNotifications.likesReceived'}}
{{d-icon "heart" class="glyph"}}{{i18n 'user_action_groups.2'}}
{{i18n 'user_action_groups.2'}}
{{/link-to}}
</li>
<li>
{{#link-to 'userNotifications.mentions'}}
{{i18n 'user_action_groups.7'}}
{{/link-to}}
</li>
<li>
{{#link-to 'userNotifications.edits'}}
{{i18n 'user_action_groups.11'}}
{{/link-to}}
</li>
<li>{{#link-to 'userNotifications.mentions'}}{{d-icon "at" class="glyph"}}{{i18n 'user_action_groups.7'}}{{/link-to}}</li>
<li>{{#link-to 'userNotifications.edits'}}{{d-icon "pencil" class="glyph"}}{{i18n 'user_action_groups.11'}}{{/link-to}}</li>
{{/mobile-nav}}
{{#if model}}

View File

@ -1,6 +1,4 @@
{{#if model.noContent}}
<div class='no-content'>
{{{model.noContentHelp}}}
</div>
<div class='alert alert-info'>{{{model.noContentHelp}}}</div>
{{/if}}
{{user-stream stream=model}}

View File

@ -92,7 +92,7 @@ createWidget('header-dropdown', jQuery.extend({
return h(
'a.icon.btn-flat',
{ attributes: {
{ attributes: {
href: attrs.href,
'data-auto-route': true,
title,
@ -150,7 +150,7 @@ createWidget('header-icons', {
});
createWidget('header-buttons', {
tagName: 'span',
tagName: 'span.header-buttons',
html(attrs) {
if (this.currentUser) { return; }

View File

@ -219,6 +219,11 @@ function replaceButton(buttons, find, replace) {
export default createWidget('post-menu', {
tagName: 'section.post-menu-area.clearfix',
settings: {
collapseButtons: true,
buttonType: 'flat-button'
},
defaultState() {
return { collapsed: true, likedUsers: [], adminVisible: false };
},
@ -230,7 +235,7 @@ export default createWidget('post-menu', {
if (builder) {
const buttonAtts = builder(attrs, this.state, this.siteSettings);
if (buttonAtts) {
return this.attach('flat-button', buttonAtts);
return this.attach(this.settings.buttonType, buttonAtts);
}
}
},
@ -258,12 +263,17 @@ export default createWidget('post-menu', {
const button = this.attachButton(i, attrs);
if (button) {
allButtons.push(button);
if ((attrs.yours && button.attrs.alwaysShowYours) || (hiddenButtons.indexOf(i) === -1)) {
visibleButtons.push(button);
}
}
});
if (!this.settings.collapseButtons) {
visibleButtons = allButtons;
}
// Only show ellipsis if there is more than one button hidden
// if there are no more buttons, we are not collapsed
if (!state.collapsed || (allButtons.length <= visibleButtons.length + 1)) {
@ -286,7 +296,7 @@ export default createWidget('post-menu', {
const { position, beforeButton } = buttonAtts;
delete buttonAtts.position;
let button = this.attach('button', buttonAtts);
let button = this.attach(this.settings.buttonType, buttonAtts);
if (beforeButton) {
button = h('span', [beforeButton(h), button]);

View File

@ -2,8 +2,33 @@ import { createWidget } from 'discourse/widgets/widget';
import RawHtml from 'discourse/widgets/raw-html';
import { iconNode } from 'discourse-common/lib/icon-library';
import { h } from 'virtual-dom';
import { actionDescriptionHtml } from 'discourse/components/small-action';
import { avatarFor } from 'discourse/widgets/post';
import { userPath } from 'discourse/lib/url';
import { autoUpdatingRelativeAge } from 'discourse/lib/formatter';
export function actionDescriptionHtml(actionCode, createdAt, username) {
const dt = new Date(createdAt);
const when = autoUpdatingRelativeAge(dt, { format: 'medium-with-ago' });
var who = "";
if (username) {
if (actionCode === "invited_group" || actionCode === "removed_group") {
who = `<a class="mention-group" href="/groups/${username}">@${username}</a>`;
} else {
who = `<a class="mention" href="${userPath(username)}">@${username}</a>`;
}
}
return I18n.t(`action_codes.${actionCode}`, { who, when }).htmlSafe();
}
export function actionDescription(actionCode, createdAt, username) {
return function() {
const ac = this.get(actionCode);
if (ac) {
return actionDescriptionHtml(ac, this.get(createdAt), this.get(username));
}
}.property(actionCode, createdAt);
}
const icons = {
'closed.enabled': 'lock',
@ -50,6 +75,7 @@ export default createWidget('post-small-action', {
if (attrs.canDelete) {
contents.push(this.attach('button', {
className: 'small-action-delete',
icon: 'times',
action: 'deletePost',
title: 'post.controls.delete'
@ -58,6 +84,7 @@ export default createWidget('post-small-action', {
if (attrs.canEdit) {
contents.push(this.attach('button', {
className: 'small-action-edit',
icon: 'pencil',
action: 'editPost',
title: 'post.controls.edit'

View File

@ -78,11 +78,20 @@ createWidget('reply-to-tab', {
});
createWidget('post-avatar-user-info', {
tagName: 'div.post-avatar-user-info',
html(attrs) {
return this.attach('poster-name', attrs);
}
});
createWidget('post-avatar', {
tagName: 'div.topic-avatar',
settings: {
size: 'large'
size: 'large',
displayPosterName: false
},
html(attrs) {
@ -106,6 +115,10 @@ createWidget('post-avatar', {
result.push(h('div.poster-avatar-extra'));
if (this.settings.displayPosterName) {
result.push(this.attach('post-avatar-user-info', attrs));
}
return result;
}
});
@ -141,8 +154,16 @@ function showReplyTab(attrs, siteSettings) {
createWidget('post-meta-data', {
tagName: 'div.topic-meta-data',
settings: {
displayPosterName: true
},
html(attrs) {
const result = [this.attach('poster-name', attrs)];
let result = [];
if (this.settings.displayPosterName) {
result.push(this.attach('poster-name', attrs));
}
if (attrs.isWhisper) {
result.push(h('div.post-info.whisper', {

View File

@ -91,6 +91,15 @@ createSearchResult({
}
});
createSearchResult({
type: 'tag',
linkField: 'url',
builder(t) {
const tag = Handlebars.Utils.escapeExpression(t.get('id'));
return h('a', { attributes: { href: t.get('url') }, className: `tag-${tag} discourse-tag ${Discourse.SiteSettings.tag_style}`}, tag);
}
});
createWidget('search-menu-results', {
tagName: 'div.results',

View File

@ -140,11 +140,12 @@ export default createWidget('topic-admin-menu', {
icon: 'lock',
label: 'actions.close' });
}
buttons.push({ className: 'topic-admin-status-update',
if (this.currentUser.get('staff')) {
buttons.push({ className: 'topic-admin-status-update',
action: 'showTopicStatusUpdate',
icon: 'clock-o',
label: 'actions.timed_update' });
}
const isPrivateMessage = topic.get('isPrivateMessage');

View File

@ -66,15 +66,19 @@ createWidget('topic-map-summary', {
contents.push(h('li',
[
h('h4', I18n.t('created_lowercase')),
avatarFor('tiny', { username: attrs.createdByUsername, template: attrs.createdByAvatarTemplate }),
dateNode(attrs.topicCreatedAt)
h('div.topic-map-post.created-at', [
avatarFor('tiny', { username: attrs.createdByUsername, template: attrs.createdByAvatarTemplate }),
dateNode(attrs.topicCreatedAt)
])
]
));
contents.push(h('li',
h('a', { attributes: { href: attrs.lastPostUrl } }, [
h('h4', I18n.t('last_reply_lowercase')),
avatarFor('tiny', { username: attrs.lastPostUsername, template: attrs.lastPostAvatarTemplate }),
dateNode(attrs.lastPostAt)
h('div.topic-map-post.last-reply', [
avatarFor('tiny', { username: attrs.lastPostUsername, template: attrs.lastPostAvatarTemplate }),
dateNode(attrs.lastPostAt)
])
])
));
contents.push(h('li', [
@ -109,7 +113,14 @@ createWidget('topic-map-summary', {
contents.push(h('li.avatars', participants));
}
return h('ul.clearfix', contents);
const nav = h('nav.buttons', this.attach('button', {
title: 'topic.toggle_information',
icon: state.collapsed ? 'chevron-down' : 'chevron-up',
action: 'toggleMap',
className: 'btn',
}));
return [nav, h('ul.clearfix', contents)];
}
});
@ -209,14 +220,7 @@ export default createWidget('topic-map', {
},
html(attrs, state) {
const nav = h('nav.buttons', this.attach('button', {
title: 'topic.toggle_information',
icon: state.collapsed ? 'chevron-down' : 'chevron-up',
action: 'toggleMap',
className: 'btn',
}));
const contents = [nav, this.attach('topic-map-summary', attrs, { state })];
const contents = [this.attach('topic-map-summary', attrs, { state })];
if (!state.collapsed) {
contents.push(this.attach('topic-map-expanded', attrs));

View File

@ -43,7 +43,7 @@ export default createWidget('topic-notifications-button', {
const details = buttonDetails(level);
const button = {
className: `btn toggle-notification-options`,
className: `toggle-notification-options`,
label: null,
icon: details.icon,
action: 'toggleDropdown',

View File

@ -7,7 +7,7 @@ export default createWidget('user-notifications', {
buildKey: () => 'user-notifications',
defaultState() {
return { notifications: [], loading: false };
return { notifications: [], loading: false, loaded: false };
},
notificationsChanged() {
@ -49,12 +49,13 @@ export default createWidget('user-notifications', {
state.notifications = [];
}).finally(() => {
state.loading = false;
state.loaded = true;
this.scheduleRerender();
});
},
html(attrs, state) {
if (!state.notifications.length) {
if (!state.loaded) {
this.refreshNotifications(state);
}

View File

@ -0,0 +1,3 @@
//= depend_on 'client.th.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:th) %>

View File

@ -14,3 +14,4 @@
//= require ./pretty-text/engines/discourse-markdown/newline
//= require ./pretty-text/engines/discourse-markdown/html-img
//= require ./pretty-text/engines/discourse-markdown/text-post-process
//= require ./pretty-text/engines/discourse-markdown/image-protocol

View File

@ -10,3 +10,4 @@
//= require ./pretty-text/sanitizer
//= require ./pretty-text/oneboxer
//= require ./pretty-text/inline-oneboxer
//= require ./pretty-text/image-short-url

View File

@ -0,0 +1,60 @@
// add image to array if src has an upload
function addImage(images, token) {
if (token.attrs) {
for(let i=0; i<token.attrs.length; i++) {
if (token.attrs[i][1].indexOf('upload://') === 0) {
images.push([token, i]);
break;
}
}
}
}
function rule(state) {
let images = [];
for (let i = 0; i < state.tokens.length; i++) {
let blockToken = state.tokens[i];
if (blockToken.tag === 'img') {
addImage(images, blockToken);
}
if (!blockToken.children) {
continue;
}
for (let j = 0; j < blockToken.children.length; j++) {
let token = blockToken.children[j];
if (token.tag === 'img') {
addImage(images, token);
}
}
}
if (images.length > 0) {
let srcList = images.map(([token, srcIndex]) => token.attrs[srcIndex][1]);
let lookup = state.md.options.discourse.lookupImageUrls;
let longUrls = (lookup && lookup(srcList)) || {};
images.forEach(([token, srcIndex]) => {
let origSrc = token.attrs[srcIndex][1];
let mapped = longUrls[origSrc];
if (mapped) {
token.attrs[srcIndex][1] = mapped;
} else {
token.attrs[srcIndex][1] = state.md.options.discourse.getURL('/images/transparent.png');
token.attrs.push(['data-orig-src', origSrc]);
}
});
}
}
export function setup(helper) {
helper.whiteList(['img[data-orig-src]']);
helper.registerPlugin(md => {
md.core.ruler.push('image-protocol', rule);
});
}

View File

@ -0,0 +1,18 @@
let _cache = {};
export function lookupCachedUploadUrl(shortUrl) {
return _cache[shortUrl];
}
export function lookupUncachedUploadUrls(urls, ajax) {
return ajax('/uploads/lookup-urls', { method: 'POST', data: { short_urls: urls } })
.then(uploads => {
uploads.forEach(upload => _cache[upload.short_url] = upload.url);
urls.forEach(url => _cache[url] = _cache[url] || "missing");
return uploads;
});
}
export function cacheShortUploadUrl(shortUrl, url) {
_cache[shortUrl] = url;
}

View File

@ -21,6 +21,7 @@ export function buildOptions(state) {
lookupAvatarByPostNumber,
emojiUnicodeReplacer,
lookupInlineOnebox,
lookupImageUrls,
previewing,
linkify,
censoredWords
@ -58,6 +59,7 @@ export function buildOptions(state) {
mentionLookup: state.mentionLookup,
emojiUnicodeReplacer,
lookupInlineOnebox,
lookupImageUrls,
censoredWords,
allowedHrefSchemes: siteSettings.allowed_href_schemes ? siteSettings.allowed_href_schemes.split('|') : null,
markdownIt: true,

View File

@ -4,7 +4,7 @@ const SIZE = 144;
let width, height;
const COLORS = ['#BF1E2E', '#F1592A', '#F7941D', '#9EB83B', '#3AB54A', '#12A89D', '#25AAE2', '#0E76BD',
const COLORS = ['#BF1E2E', '#F1592A', '#F7941D', '#9EB83B', '#3AB54A', '#12A89D', '#25AAE2', '#0E76BD',
'#652D90', '#92278F', '#ED207B', '#8C6238'];
class Particle {
@ -45,7 +45,7 @@ export default Ember.Component.extend({
tagName: 'canvas',
ctx: null,
ready: false,
particles: null,
particles: null,
didInsertElement() {
this._super();

View File

@ -36,8 +36,8 @@ $mobile-breakpoint: 700px;
tr {text-align: left;}
td, th {padding: 8px;}
td {
border-bottom: 1px solid $primary-low;
border-top: 1px solid $primary-low;
border-bottom: 1px solid $primary-low;
border-top: 1px solid $primary-low;
}
th {
text-align: left;
@ -94,7 +94,7 @@ td.flaggers td {
.site-text {
cursor: pointer;
border-bottom: 1px solid $primary-low;
border-bottom: 1px solid $primary-low;
margin-bottom: 0.5em;
&.overridden {
@ -112,7 +112,7 @@ td.flaggers td {
.site-text-value {
margin: 0.5em 5em 0.5em 0;
max-height: 100px;
color: $primary-medium;
color: $primary-medium;
}
}
@ -136,7 +136,7 @@ td.flaggers td {
font-size: 0.857em;
float: right;
margin-right: 10px;
background-color: $primary-low;
background-color: $primary-low;
padding: 2px 5px;
border-radius: 5px;
color: $primary;
@ -193,6 +193,13 @@ td.flaggers td {
background-color: $secondary;
padding: 12px 12px 5px;
.powered-by {
font-size: 0.80em;
position: absolute;
bottom: -10px;
left: 10px;
}
.other-accounts {
margin: 5px 0 0;
max-height: 200px;
@ -222,16 +229,16 @@ td.flaggers td {
}
.admin-controls {
background-color: $primary-low;
background-color: $primary-low;
padding: 10px 10px 3px 0;
@include clearfix;
.nav.nav-pills {
li.active {
a {
border-color: $primary-low;
background-color: $primary-medium;
border-color: $primary-low;
background-color: $primary-medium;
&:hover {
background-color: $primary-medium;
background-color: $primary-medium;
}
}
}
@ -342,7 +349,7 @@ td.flaggers td {
margin-top: 10px;
}
input, textarea, select {
input, textarea, select, .select-box {
width: 350px;
}
@ -367,23 +374,13 @@ td.flaggers td {
.admin-nav {
width: 18.018%;
position: relative;
// The admin-nav becomes a slide-out menu at the mobile-nav breakpoint
@media (max-width: $mobile-breakpoint) {
position: absolute;
z-index: 0;
width: 50%;
}
margin-top: 30px;
.nav-stacked {
border-right: none;
@media (max-width: $mobile-breakpoint) {
//margin-right: 10px;
}
}
li a.active {
color: $secondary;
background-color: $quaternary;
}
}
.admin-detail {
@ -396,7 +393,7 @@ td.flaggers td {
// Todo: set this properly - it needs to be >= the menu height
min-height: 875px;
margin-left: 0;
border-left: solid 1px $primary-low;
border-left: solid 1px $primary-low;
padding: 30px 0 30px 30px;
@media (max-width: $mobile-breakpoint) {
padding: 30px 0;
@ -478,7 +475,7 @@ td.flaggers td {
}
padding: 1px;
background-color: $secondary;
border: 1px solid $primary-low;
border: 1px solid $primary-low;
border-radius: 3px;
box-shadow: inset 0 1px 1px rgba(51, 51, 51, 0.3);
transition: border linear 0.2s, box-shadow linear 0.2s;
@ -546,7 +543,7 @@ section.details {
color: $primary;
padding: 5px 10px;
margin: 30px 0 5px 0;
border-bottom: 5px solid $primary-low;
border-bottom: 5px solid $primary-low;
}
}
@ -589,7 +586,7 @@ section.details {
&.highlight-danger {
background-color: scale-color($danger, $lightness: 50%);
}
border-top: 1px solid $primary-low;
border-top: 1px solid $primary-low;
&:before, &:after {
display: table;
content: "";
@ -709,14 +706,14 @@ section.details {
font-size: 1em;
line-height: 16px;
padding: 4px;
background-color: $primary-low;
background-color: $primary-low;
}
.badge-query-plan {
font-size: 0.857em;
line-height: 13px;
padding: 4px;
background-color: $primary-low;
background-color: $primary-low;
}
.count-warning {
@ -915,7 +912,7 @@ table.api-keys {
th {
font-weight: normal;
text-align: center;
background: $primary-low;
background: $primary-low;
}
th.title {
text-align: left;
@ -965,7 +962,7 @@ table.api-keys {
}
&.detected-problems {
background: $primary-low;
background: $primary-low;
margin-bottom: 20px;
.look-here {
@ -990,7 +987,7 @@ table.api-keys {
text-align: right;
}
.btn {
background: $primary-low;
background: $primary-low;
}
ul {
margin-left: 0;
@ -1040,7 +1037,7 @@ table.api-keys {
}
.commits-widget {
border: solid 1px $primary-low;
border: solid 1px $primary-low;
height: 180px;
margin-bottom: 36px;
@ -1063,7 +1060,7 @@ table.api-keys {
color: $primary;
font-weight: bold;
height: 30px;
background: $primary-low;
background: $primary-low;
cursor: pointer;
h1 {
@ -1086,7 +1083,7 @@ table.api-keys {
@extend .clearfix;
line-height: 1.0em;
padding: 6px 8px;
background-color: $primary-low;
background-color: $primary-low;
.left {
float: left;
}
@ -1096,7 +1093,7 @@ table.api-keys {
img {
margin-top: 2px;
border: solid 1px $primary-low;
border: solid 1px $primary-low;
padding: 2px;
background-color: $secondary;
}
@ -1127,11 +1124,11 @@ table.api-keys {
width: 6px;
}
::-webkit-scrollbar-thumb {
background: $primary-low;
background: $primary-low;
-webkit-border-radius: 3px;
}
::-webkit-scrollbar-track {
border-left: solid 1px $primary-low;
border-left: solid 1px $primary-low;
}
}
@ -1306,7 +1303,7 @@ table.api-keys {
color: $primary;
&:hover {
color: $primary;
background-color: $primary-low;
background-color: $primary-low;
}
@ -1336,7 +1333,7 @@ table.api-keys {
.heading-container {
width: 100%;
background-color: $primary-low;
background-color: $primary-low;
}
.col.heading {
font-weight: bold;
@ -1352,7 +1349,7 @@ table.api-keys {
.ember-list-item-view {
width: 100%;
border-top: solid 1px $primary-low;
border-top: solid 1px $primary-low;
}
}
@ -1440,14 +1437,6 @@ button.ru {
}
}
@media all
and (max-width : 850px) {
html:not(.mobile-view) .admin-content .nav-stacked {
.glyph {width: auto; position: relative;}
> li > a {padding: 13px}
}
}
@media all
and (min-width : 320px)
and (max-width : 500px) {
@ -1542,7 +1531,7 @@ tr.not-activated {
.user-field {
padding: 10px;
margin-bottom: 10px;
border-bottom: 1px solid $primary-low;
border-bottom: 1px solid $primary-low;
.form-display {
width: 25%;
@ -1678,6 +1667,10 @@ table#user-badges {
}
}
.embedding td input {
margin-bottom: 0;
}
// Emails
.email-list {

View File

@ -45,153 +45,154 @@ html {
}
body {
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: .5rem;
}
background-attachment: fixed;
background-size: cover;
min-height: 100%;
@include clearfix;
}
button.ok {
background: $success;
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: .5rem;
}
button.ok {
background: $success;
color: $secondary;
@include hover {
background: lighten($success, 10%);
color: $secondary;
@include hover {
background: lighten($success, 10%);
color: $secondary;
}
}
button.cancel {
background: $danger;
}
button.cancel {
background: $danger;
color: $secondary;
@include hover {
background: lighten($danger, 10%);
color: $secondary;
@include hover {
background: lighten($danger, 10%);
color: $secondary;
}
}
}
// the default for table cells in topic list
// is scale-color($primary, $lightness: 50%)
// numbers get dimmer as they get colder
.coldmap-high {
color: dark-light-choose(scale-color($primary, $lightness: 70%), scale-color($secondary, $lightness: 30%)) !important;
}
.coldmap-med {
color: dark-light-choose(scale-color($primary, $lightness: 60%), scale-color($secondary, $lightness: 40%)) !important;
}
.coldmap-low {
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%)) !important;
}
// the default for table cells in topic list
// is scale-color($primary, $lightness: 50%)
// numbers get dimmer as they get colder
.coldmap-high {
color: dark-light-choose(scale-color($primary, $lightness: 70%), scale-color($secondary, $lightness: 30%)) !important;
}
.coldmap-med {
color: dark-light-choose(scale-color($primary, $lightness: 60%), scale-color($secondary, $lightness: 40%)) !important;
}
.coldmap-low {
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%)) !important;
}
#loading-message {
position: absolute;
font-size: 2.143em;
text-align: center;
top: 120px;
left: 500px;
color: $primary;
}
.top-space {
margin-top: 10px;
}
ul.breadcrumb {
margin: 0 10px 0 10px;
}
#loading-message {
position: absolute;
font-size: 2.143em;
text-align: center;
top: 120px;
left: 500px;
color: $primary;
}
.top-space {
margin-top: 10px;
}
ul.breadcrumb {
margin: 0 10px 0 10px;
}
.message {
@include border-radius-all(8px);
background-color: $secondary;
padding: 14px;
.message {
@include border-radius-all(8px);
background-color: $secondary;
padding: 14px;
h2 {
margin-bottom: 20px;
}
h2 {
margin-bottom: 20px;
}
}
#footer {
.container {
height: 50px;
.contents {
padding-top: 10px;
a[href] {
color: $secondary;
}
#footer {
.container {
height: 50px;
.contents {
padding-top: 10px;
a[href] {
color: $secondary;
}
}
}
}
.clear-transitions {
transition:none !important;
}
.clear-transitions {
transition:none !important;
}
.tip {
display: inline-block;
&.good {
color: $success;
}
&.bad {
color: $danger;
}
.tip {
display: inline-block;
&.good {
color: $success;
}
&.bad {
color: $danger;
}
}
input[type].invalid {
background-color: dark-light-choose(scale-color($danger, $lightness: 80%), scale-color($danger, $lightness: -60%));
}
input[type].invalid {
background-color: dark-light-choose(scale-color($danger, $lightness: 80%), scale-color($danger, $lightness: -60%));
}
.d-editor-input {
resize: none;
}
.d-editor-input {
resize: none;
}
.avatar-wrapper {
background-color: $secondary;
display: inline-block;
border-radius: 50%;
}
.avatar-wrapper {
background-color: $secondary;
display: inline-block;
border-radius: 50%;
}
.profiler-results.profiler-left {
top: 60px !important;
}
.profiler-results.profiler-left {
top: 60px !important;
}
label {
display: block;
margin-bottom: 5px;
label {
display: block;
margin-bottom: 5px;
}
input {
&[type="radio"], &[type="checkbox"] {
margin: 3px 0;
line-height: normal;
cursor: pointer;
}
input {
&[type="radio"], &[type="checkbox"] {
margin: 3px 0;
line-height: normal;
cursor: pointer;
}
&[type="submit"], &[type="reset"], &[type="button"], &[type="radio"], &[type="checkbox"] {
width: auto;
}
&[type="submit"], &[type="reset"], &[type="button"], &[type="radio"], &[type="checkbox"] {
width: auto;
}
.radio, .checkbox {
min-height: 18px;
padding-left: 18px;
}
.radio input[type="radio"], .checkbox input[type="checkbox"] {
float: left;
margin-left: -18px;
}
.controls > {
.radio:first-child, .checkbox:first-child {
padding-top: 5px;
}
}
.radio.inline, .checkbox.inline {
display: inline-block;
}
.radio, .checkbox {
min-height: 18px;
padding-left: 18px;
}
.radio input[type="radio"], .checkbox input[type="checkbox"] {
float: left;
margin-left: -18px;
}
.controls > {
.radio:first-child, .checkbox:first-child {
padding-top: 5px;
margin-bottom: 0;
vertical-align: middle;
}
.radio.inline .radio.inline, .checkbox.inline .checkbox.inline {
margin-left: 10px;
}
}
.radio.inline, .checkbox.inline {
display: inline-block;
padding-top: 5px;
margin-bottom: 0;
vertical-align: middle;
}
.radio.inline .radio.inline, .checkbox.inline .checkbox.inline {
margin-left: 10px;
}
.flex-center-align {
display: flex;
@ -329,7 +330,7 @@ body {
.content-list {
h3 {
color: $primary-medium;
color: $primary-medium;
font-size: 1.071em;
padding-left: 5px;
margin-bottom: 10px;
@ -340,10 +341,10 @@ body {
margin: 0;
li:first-of-type {
border-top: 1px solid $primary-low;
border-top: 1px solid $primary-low;
}
li {
border-bottom: 1px solid $primary-low;
border-bottom: 1px solid $primary-low;
}
li a {
@ -352,7 +353,7 @@ body {
color: $primary;
&:hover {
background-color: $primary-low;
background-color: $primary-low;
color: $primary;
}

View File

@ -51,7 +51,7 @@ table.group-logs {
width: 100%;
th, tr {
border-bottom: 1px solid $primary-low;
border-bottom: 1px solid $primary-low;
}
th {
@ -77,7 +77,7 @@ table.group-members {
table-layout: fixed;
tr {
border-bottom: 1px solid $primary-low;
border-bottom: 1px solid $primary-low;
}
th:first-child {
@ -90,7 +90,7 @@ table.group-members {
}
th {
border-bottom: 3px solid $primary-low;
border-bottom: 3px solid $primary-low;
text-align: center;
padding: 5px 0px 5px 5px;
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
@ -104,7 +104,7 @@ table.group-members {
&:hover {
cursor: pointer;
background-color: $primary-low;
background-color: $primary-low;
}
}
@ -165,15 +165,3 @@ table.group-members {
}
}
}
#add-user-to-group {
margin: 0px;
.ac-wrap {
width: 100% !important;
}
.add {
margin-top: 10px;
}
}

Some files were not shown because too many files have changed in this diff Show More