Merge branch 'master' into beta
This commit is contained in:
commit
1e5d451cb1
@ -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
5
.gitignore
vendored
@ -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
|
||||
|
||||
39
.travis.yml
39
.travis.yml
@ -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
|
||||
"
|
||||
|
||||
2
Gemfile
2
Gemfile
@ -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
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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"}}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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'));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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") {
|
||||
|
||||
@ -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})} ${result}`;
|
||||
} else {
|
||||
result = `<div class="category-status">${result}`;
|
||||
}
|
||||
|
||||
result += ` <span class="topic-count">× ${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 ? '…' : ''}</div>`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
@ -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");
|
||||
}
|
||||
});
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
import SelectBoxComponent from "discourse/components/select-box";
|
||||
|
||||
export default SelectBoxComponent.extend({
|
||||
layoutName: "components/select-box",
|
||||
|
||||
classNames: "discourse"
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
@ -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' : '';
|
||||
|
||||
@ -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");
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
export default Ember.Component.extend({
|
||||
classNames: "select-box-collection"
|
||||
classNames: "select-box-collection",
|
||||
|
||||
actions: {
|
||||
onClearSelection() {
|
||||
this.sendAction("onClearSelection");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
export default Ember.Component.extend({
|
||||
classNames: "select-box-filter"
|
||||
classNames: "select-box-filter",
|
||||
|
||||
classNameBindings: ["focused:is-focused"]
|
||||
});
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
});
|
||||
|
||||
@ -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'));
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
@ -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();
|
||||
},
|
||||
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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"),
|
||||
});
|
||||
@ -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');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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', []);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 ``;
|
||||
return ``;
|
||||
} 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 {
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'
|
||||
});
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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}}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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"}}
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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"}}
|
||||
|
||||
@ -1,7 +1 @@
|
||||
{{#if content.icon}}
|
||||
{{d-icon content.icon}}
|
||||
{{/if}}
|
||||
|
||||
<p class="text">
|
||||
{{content.text}}
|
||||
</p>
|
||||
{{{template}}}
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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}}
|
||||
@ -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}}
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}}>
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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"}}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}}
|
||||
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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}}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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', {
|
||||
|
||||
@ -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',
|
||||
|
||||
|
||||
@ -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');
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
3
app/assets/javascripts/locales/th.js.erb
Normal file
3
app/assets/javascripts/locales/th.js.erb
Normal file
@ -0,0 +1,3 @@
|
||||
//= depend_on 'client.th.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:th) %>
|
||||
@ -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
|
||||
|
||||
@ -10,3 +10,4 @@
|
||||
//= require ./pretty-text/sanitizer
|
||||
//= require ./pretty-text/oneboxer
|
||||
//= require ./pretty-text/inline-oneboxer
|
||||
//= require ./pretty-text/image-short-url
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
18
app/assets/javascripts/pretty-text/image-short-url.js.es6
Normal file
18
app/assets/javascripts/pretty-text/image-short-url.js.es6
Normal 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;
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
Reference in New Issue
Block a user