Version bump
This commit is contained in:
commit
80535b09f1
@ -55,4 +55,4 @@ install:
|
||||
- bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails rails-observers seed-fu; fi"
|
||||
- bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi"
|
||||
|
||||
script: 'bundle exec rspec && bundle exec rake plugin:spec && bundle exec rake qunit:test'
|
||||
script: "bundle exec rspec && bundle exec rake plugin:spec && bundle exec rake qunit:test['200000']"
|
||||
|
||||
@ -212,7 +212,7 @@ GEM
|
||||
omniauth-twitter (1.2.1)
|
||||
json (~> 1.3)
|
||||
omniauth-oauth (~> 1.1)
|
||||
onebox (1.6.0)
|
||||
onebox (1.6.2)
|
||||
htmlentities (~> 4.3.4)
|
||||
moneta (~> 0.8)
|
||||
multi_json (~> 1.11)
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import { propertyEqual } from 'discourse/lib/computed';
|
||||
import { escapeExpression } from 'discourse/lib/utilities';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
adminGroupsType: Ember.inject.controller(),
|
||||
@ -37,43 +35,6 @@ export default Ember.Controller.extend({
|
||||
];
|
||||
}.property(),
|
||||
|
||||
@computed
|
||||
demoAvatarUrl() {
|
||||
return Discourse.getURL('/images/avatar.png');
|
||||
},
|
||||
|
||||
@computed('model.flair_url')
|
||||
flairPreviewIcon() {
|
||||
return this.get('model.flair_url') && this.get('model.flair_url').substr(0,3) === 'fa-';
|
||||
},
|
||||
|
||||
@computed('flairPreviewIcon')
|
||||
flairPreviewImage() {
|
||||
return this.get('model.flair_url') && !this.get('flairPreviewIcon');
|
||||
},
|
||||
|
||||
@computed('flairPreviewImage', 'model.flair_url', 'model.flairBackgroundHexColor', 'model.flairHexColor')
|
||||
flairPreviewStyle() {
|
||||
var style = '';
|
||||
if (this.get('flairPreviewImage')) {
|
||||
style += 'background-image: url(' + escapeExpression(this.get('model.flair_url')) + '); ';
|
||||
}
|
||||
if (this.get('model.flairBackgroundHexColor')) {
|
||||
style += 'background-color: #' + this.get('model.flairBackgroundHexColor') + ';';
|
||||
}
|
||||
if (this.get('model.flairHexColor')) {
|
||||
style += 'color: #' + this.get('model.flairHexColor') + ';';
|
||||
}
|
||||
return style;
|
||||
},
|
||||
|
||||
@computed('model.flairBackgroundHexColor')
|
||||
flairPreviewClasses() {
|
||||
if (this.get('model.flairBackgroundHexColor')) {
|
||||
return 'rounded';
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
next() {
|
||||
if (this.get("showingLast")) { return; }
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import debounce from 'discourse/lib/debounce';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
queryParams: ["filter"],
|
||||
filter: null,
|
||||
onlyOverridden: false,
|
||||
filtered: Ember.computed.notEmpty('filter'),
|
||||
|
||||
@ -6,12 +6,8 @@ export default Ember.Controller.extend({
|
||||
fieldTypes: null,
|
||||
createDisabled: Em.computed.gte('model.length', MAX_FIELDS),
|
||||
|
||||
arrangedContent: function() {
|
||||
return Ember.ArrayProxy.extend(Ember.SortableMixin).create({
|
||||
sortProperties: ['position'],
|
||||
content: this.get('model')
|
||||
});
|
||||
}.property('model'),
|
||||
fieldSortOrder: ['position'],
|
||||
sortedFields: Ember.computed.sort('model', 'fieldSortOrder'),
|
||||
|
||||
actions: {
|
||||
createField() {
|
||||
@ -20,9 +16,9 @@ export default Ember.Controller.extend({
|
||||
},
|
||||
|
||||
moveUp(f) {
|
||||
const idx = this.get('arrangedContent').indexOf(f);
|
||||
const idx = this.get('sortedFields').indexOf(f);
|
||||
if (idx) {
|
||||
const prev = this.get('arrangedContent').objectAt(idx-1);
|
||||
const prev = this.get('sortedFields').objectAt(idx-1);
|
||||
const prevPos = prev.get('position');
|
||||
|
||||
prev.update({ position: f.get('position') });
|
||||
@ -31,9 +27,9 @@ export default Ember.Controller.extend({
|
||||
},
|
||||
|
||||
moveDown(f) {
|
||||
const idx = this.get('arrangedContent').indexOf(f);
|
||||
const idx = this.get('sortedFields').indexOf(f);
|
||||
if (idx > -1) {
|
||||
const next = this.get('arrangedContent').objectAt(idx+1);
|
||||
const next = this.get('sortedFields').objectAt(idx+1);
|
||||
const nextPos = next.get('position');
|
||||
|
||||
next.update({ position: f.get('position') });
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
export default {
|
||||
resource: 'admin',
|
||||
|
||||
map() {
|
||||
export default function() {
|
||||
this.route('admin', { resetNamespace: true }, function() {
|
||||
this.route('dashboard', { path: '/' });
|
||||
this.route('adminSiteSettings', { path: '/site_settings', resetNamespace: true }, function() {
|
||||
this.route('adminSiteSettingsCategory', { path: 'category/:category_id', resetNamespace: true} );
|
||||
@ -84,5 +82,9 @@ export default {
|
||||
this.route('adminBadges', { path: '/badges', resetNamespace: true }, function() {
|
||||
this.route('show', { path: '/:badge_id' });
|
||||
});
|
||||
}
|
||||
|
||||
this.route('adminPlugins', { path: '/plugins', resetNamespace: true }, function() {
|
||||
this.route('index', { path: '/' });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -4,13 +4,18 @@
|
||||
{{#if model.automatic}}
|
||||
<h3>{{model.name}}</h3>
|
||||
{{else}}
|
||||
<label for="name">{{i18n 'admin.groups.name'}}</label>
|
||||
{{text-field name="name" value=model.name placeholderKey="admin.groups.name_placeholder"}}
|
||||
<label for="name">{{i18n 'group.name'}}</label>
|
||||
{{text-field name="name" value=model.name placeholderKey="group.name_placeholder"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if model.id}}
|
||||
{{#unless model.automatic}}
|
||||
<div>
|
||||
<label for="bio">{{i18n 'group.bio'}}</label>
|
||||
{{d-editor value=model.bio_raw}}
|
||||
</div>
|
||||
|
||||
{{#if model.hasOwners}}
|
||||
<div>
|
||||
<label for='owner-list'>{{i18n 'admin.groups.group_owners'}}</label>
|
||||
@ -101,63 +106,7 @@
|
||||
{{/unless}}
|
||||
|
||||
{{#unless model.automatic}}
|
||||
<div class="flair_inputs">
|
||||
<div class="flair_left">
|
||||
<div>
|
||||
<label for="flair_url">{{i18n 'admin.groups.flair_url'}}</label>
|
||||
{{text-field name="flair_url" value=model.flair_url placeholderKey="admin.groups.flair_url_placeholder"}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="flair_bg_color">{{i18n 'admin.groups.flair_bg_color'}}</label>
|
||||
{{text-field name="flair_bg_color" class="flair_bg_color" value=model.flair_bg_color placeholderKey="admin.groups.flair_bg_color_placeholder"}}
|
||||
</div>
|
||||
|
||||
{{#if flairPreviewIcon}}
|
||||
<div>
|
||||
<label for="flair_color">{{i18n 'admin.groups.flair_color'}}</label>
|
||||
{{text-field name="flair_color" class="flair_color" value=model.flair_color placeholderKey="admin.groups.flair_color_placeholder"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<br/>
|
||||
<div>
|
||||
<strong>{{i18n 'admin.groups.flair_note'}}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if flairPreviewIcon}}
|
||||
<div class="flair_right">
|
||||
<div>
|
||||
<label>{{i18n 'admin.groups.flair_preview'}} Icon</label>
|
||||
<div class="avatar-flair-preview">
|
||||
<div class="avatar-wrapper">
|
||||
<img alt width="45" height="45" src="{{demoAvatarUrl}}" class="avatar actor">
|
||||
</div>
|
||||
<div class="avatar-flair demo {{flairPreviewClasses}}" style={{flairPreviewStyle}}>
|
||||
<i class="fa {{model.flair_url}}"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if flairPreviewImage}}
|
||||
<div class="flair_right">
|
||||
<div>
|
||||
<label>{{i18n 'admin.groups.flair_preview'}} Image</label>
|
||||
<div class="avatar-flair-preview">
|
||||
<div class="avatar-wrapper">
|
||||
<img alt width="45" height="45" src="{{demoAvatarUrl}}" class="avatar actor">
|
||||
</div>
|
||||
<div class="avatar-flair demo {{flairPreviewClasses}}" style={{flairPreviewStyle}}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{{group-flair-inputs model=model}}
|
||||
{{/unless}}
|
||||
|
||||
<div class='buttons'>
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
<p class="desc">{{i18n 'admin.user_fields.help'}}</p>
|
||||
|
||||
{{#if model}}
|
||||
{{#each arrangedContent as |uf|}}
|
||||
{{#each sortedFields as |uf|}}
|
||||
{{admin-user-field-item userField=uf
|
||||
fieldTypes=fieldTypes
|
||||
firstField=arrangedContent.firstObject
|
||||
lastField=arrangedContent.lastObject
|
||||
firstField=sortedFields.firstObject
|
||||
lastField=sortedFields.lastObject
|
||||
destroyAction="destroy"
|
||||
moveUpAction="moveUp"
|
||||
moveDownAction="moveDown"}}
|
||||
|
||||
@ -135,7 +135,7 @@ export function buildResolver(baseName) {
|
||||
},
|
||||
|
||||
findPluginTemplate(parsedName) {
|
||||
var pluginParsedName = this.parseName(parsedName.fullName.replace("template:", "template:javascripts/"));
|
||||
const pluginParsedName = this.parseName(parsedName.fullName.replace("template:", "template:javascripts/"));
|
||||
return this.findTemplate(pluginParsedName);
|
||||
},
|
||||
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import { observes } from 'ember-addons/ember-computed-decorators';
|
||||
import MountWidget from 'discourse/components/mount-widget';
|
||||
|
||||
export default MountWidget.extend({
|
||||
widget: 'avatar-flair',
|
||||
|
||||
@observes('flairURL', 'flairBgColor', 'flairColor')
|
||||
_rerender() {
|
||||
this.queueRerender();
|
||||
},
|
||||
|
||||
buildArgs() {
|
||||
return {
|
||||
primary_group_flair_url: this.get('flairURL'),
|
||||
primary_group_flair_bg_color: this.get('flairBgColor'),
|
||||
primary_group_flair_color: this.get('flairColor'),
|
||||
primary_group_name: this.get('groupName')
|
||||
};
|
||||
}
|
||||
});
|
||||
@ -13,7 +13,8 @@ export default Ember.Component.extend({
|
||||
'composer.canEditTitle:edit-title',
|
||||
'composer.createdPost:created-post',
|
||||
'composer.creatingTopic:topic',
|
||||
'composer.whisper:composing-whisper'],
|
||||
'composer.whisper:composing-whisper',
|
||||
'composer.showComposerEditor::topic-featured-link-only'],
|
||||
|
||||
@computed('composer.composeState')
|
||||
composeState(composeState) {
|
||||
@ -27,7 +28,7 @@ export default Ember.Component.extend({
|
||||
this.appEvents.trigger("composer:resized");
|
||||
},
|
||||
|
||||
@observes('composeState', 'composer.action')
|
||||
@observes('composeState', 'composer.action', 'composer.canEditTopicFeaturedLink')
|
||||
resize() {
|
||||
Ember.run.scheduleOnce('afterRender', () => {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) { return; }
|
||||
@ -76,6 +77,13 @@ export default Ember.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@observes('composeState')
|
||||
disableFullscreen() {
|
||||
if (this.get('composeState') !== Composer.OPEN && positioningWorkaround.blur) {
|
||||
positioningWorkaround.blur();
|
||||
}
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
const $replyControl = $('#reply-control');
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import UploadMixin from "discourse/mixins/upload";
|
||||
|
||||
export default Em.Component.extend(UploadMixin, {
|
||||
type: "csv",
|
||||
tagName: "span",
|
||||
uploadUrl: "/invites/upload_csv",
|
||||
|
||||
@computed("uploading")
|
||||
uploadButtonText(uploading) {
|
||||
return uploading ? I18n.t("uploading") : I18n.t("user.invited.bulk_invite.text");
|
||||
},
|
||||
|
||||
uploadDone() {
|
||||
bootbox.alert(I18n.t("user.invited.bulk_invite.success"));
|
||||
}
|
||||
});
|
||||
@ -28,7 +28,7 @@ export default Ember.Component.extend({
|
||||
}
|
||||
}
|
||||
|
||||
this.appEvents.trigger('modal:body-shown', this.getProperties('title'));
|
||||
this.appEvents.trigger('modal:body-shown', this.getProperties('title', 'rawTitle'));
|
||||
},
|
||||
|
||||
_flash(msg) {
|
||||
|
||||
@ -19,6 +19,8 @@ export default Ember.Component.extend({
|
||||
this.appEvents.on('modal:body-shown', data => {
|
||||
if (data.title) {
|
||||
this.set('title', I18n.t(data.title));
|
||||
} else if (data.rawTitle) {
|
||||
this.set('title', data.rawTitle);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { escapeExpression } from 'discourse/lib/utilities';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
|
||||
classNames: ['group-flair-inputs'],
|
||||
|
||||
@computed
|
||||
demoAvatarUrl() {
|
||||
return Discourse.getURL('/images/avatar.png');
|
||||
},
|
||||
|
||||
@computed('model.flair_url')
|
||||
flairPreviewIcon(flairURL) {
|
||||
return flairURL && flairURL.substr(0,3) === 'fa-';
|
||||
},
|
||||
|
||||
@computed('model.flair_url', 'flairPreviewIcon')
|
||||
flairPreviewImage(flairURL, flairPreviewIcon) {
|
||||
return flairURL && !flairPreviewIcon;
|
||||
},
|
||||
|
||||
@computed('model.flair_url', 'flairPreviewImage', 'model.flairBackgroundHexColor', 'model.flairHexColor')
|
||||
flairPreviewStyle(flairURL, flairPreviewImage, flairBackgroundHexColor, flairHexColor) {
|
||||
let style = '';
|
||||
|
||||
if (flairPreviewImage) {
|
||||
style += `background-image: url(${escapeExpression(flairURL)});`;
|
||||
}
|
||||
|
||||
if (flairBackgroundHexColor) {
|
||||
style += `background-color: #${flairBackgroundHexColor};`;
|
||||
}
|
||||
|
||||
if (flairHexColor) style += `color: #${flairHexColor};`;
|
||||
|
||||
return style;
|
||||
},
|
||||
|
||||
@computed('model.flairBackgroundHexColor')
|
||||
flairPreviewClasses(flairBackgroundHexColor) {
|
||||
if (flairBackgroundHexColor) return 'rounded';
|
||||
},
|
||||
|
||||
@computed('flairPreviewImage')
|
||||
flairPreviewLabel(flairPreviewImage) {
|
||||
const key = flairPreviewImage ? 'image' : 'icon';
|
||||
return I18n.t(`group.flair_preview_${key}`);
|
||||
}
|
||||
});
|
||||
@ -1,4 +1,5 @@
|
||||
import { findAll } from 'discourse/models/login-method';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
elementId: 'login-buttons',
|
||||
@ -6,9 +7,10 @@ export default Ember.Component.extend({
|
||||
|
||||
hidden: Ember.computed.equal('buttons.length', 0),
|
||||
|
||||
buttons: function() {
|
||||
return findAll(this.siteSettings);
|
||||
}.property(),
|
||||
@computed
|
||||
buttons() {
|
||||
return findAll(this.siteSettings, this.capabilities, this.site.isMobileDevice);
|
||||
},
|
||||
|
||||
actions: {
|
||||
externalLogin: function(provider) {
|
||||
|
||||
@ -15,6 +15,7 @@ export default Ember.Component.extend({
|
||||
visible: buffer => buffer && buffer.length > 0,
|
||||
|
||||
_isMouseDown: false,
|
||||
_reselected: false,
|
||||
|
||||
_selectionChanged() {
|
||||
const selection = window.getSelection();
|
||||
@ -43,9 +44,12 @@ export default Ember.Component.extend({
|
||||
// on Desktop, shows the button at the beginning of the selection
|
||||
// on Mobile, shows the button at the end of the selection
|
||||
const isMobileDevice = this.site.isMobileDevice;
|
||||
const { isIOS, isAndroid } = this.capabilities;
|
||||
const { isIOS, isAndroid, isSafari } = this.capabilities;
|
||||
const showAtEnd = isMobileDevice || isIOS || isAndroid;
|
||||
|
||||
// used to work around Safari losing selection
|
||||
const clone = firstRange.cloneRange();
|
||||
|
||||
// create a marker element containing a single invisible character
|
||||
const markerElement = document.createElement("span");
|
||||
markerElement.appendChild(document.createTextNode("\ufeff"));
|
||||
@ -64,6 +68,13 @@ export default Ember.Component.extend({
|
||||
// remove the marker
|
||||
markerElement.parentNode.removeChild(markerElement);
|
||||
|
||||
// work around Safari that would sometimes lose the selection
|
||||
if (isSafari) {
|
||||
this._reselected = true;
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(clone);
|
||||
}
|
||||
|
||||
// change the position of the button
|
||||
Ember.run.scheduleOnce("afterRender", () => {
|
||||
let top = markerOffset.top;
|
||||
@ -88,6 +99,7 @@ export default Ember.Component.extend({
|
||||
|
||||
$(document).on("mousedown.quote-button", (e) => {
|
||||
this._isMouseDown = true;
|
||||
this._reselected = false;
|
||||
if (!willQuote(e)) {
|
||||
this.sendAction("deselectText");
|
||||
}
|
||||
@ -95,7 +107,7 @@ export default Ember.Component.extend({
|
||||
this._isMouseDown = false;
|
||||
onSelectionChanged();
|
||||
}).on("selectionchange.quote-button", () => {
|
||||
if (!this._isMouseDown) {
|
||||
if (!this._isMouseDown && !this._reselected) {
|
||||
onSelectionChanged();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
const REGEXP_BLOCKS = /(([^" \t\n\x0B\f\r]+)?(("[^"]+")?))/g;
|
||||
const REGEXP_BLOCKS = /(([^" \t\n\x0B\f\r]+)?(("[^"]+")?))/g;
|
||||
|
||||
const REGEXP_USERNAME_PREFIX = /(user:|@)/ig;
|
||||
const REGEXP_CATEGORY_PREFIX = /(category:|#)/ig;
|
||||
const REGEXP_GROUP_PREFIX = /group:/ig;
|
||||
const REGEXP_BADGE_PREFIX = /badge:/ig;
|
||||
const REGEXP_TAGS_PREFIX = /tags?:/ig;
|
||||
const REGEXP_IN_PREFIX = /in:/ig;
|
||||
const REGEXP_STATUS_PREFIX = /status:/ig;
|
||||
const REGEXP_POST_COUNT_PREFIX = /posts_count:/ig;
|
||||
const REGEXP_POST_TIME_PREFIX = /(before|after):/ig;
|
||||
const REGEXP_USERNAME_PREFIX = /(user:|@)/ig;
|
||||
const REGEXP_CATEGORY_PREFIX = /(category:|#)/ig;
|
||||
const REGEXP_GROUP_PREFIX = /group:/ig;
|
||||
const REGEXP_BADGE_PREFIX = /badge:/ig;
|
||||
const REGEXP_TAGS_PREFIX = /tags?:/ig;
|
||||
const REGEXP_IN_PREFIX = /in:/ig;
|
||||
const REGEXP_STATUS_PREFIX = /status:/ig;
|
||||
const REGEXP_MIN_POST_COUNT_PREFIX = /min_post_count:/ig;
|
||||
const REGEXP_POST_TIME_PREFIX = /(before|after):/ig;
|
||||
|
||||
const REGEXP_IN_MATCH = /in:(posted|watching|tracking|bookmarks|first|pinned|unpinned)/ig;
|
||||
const REGEXP_SPECIAL_IN_LIKES_MATCH = /in:likes/ig;
|
||||
@ -73,7 +73,7 @@ export default Em.Component.extend({
|
||||
}
|
||||
},
|
||||
status: '',
|
||||
posts_count: '',
|
||||
min_post_count: '',
|
||||
time: {
|
||||
when: 'before',
|
||||
days: ''
|
||||
@ -99,7 +99,7 @@ export default Em.Component.extend({
|
||||
this.setSearchedTermSpecialInValue('searchedTerms.special.in.wiki', REGEXP_SPECIAL_IN_WIKI_MATCH);
|
||||
this.setSearchedTermValue('searchedTerms.status', REGEXP_STATUS_PREFIX);
|
||||
this.setSearchedTermValueForPostTime();
|
||||
this.setSearchedTermValue('searchedTerms.posts_count', REGEXP_POST_COUNT_PREFIX);
|
||||
this.setSearchedTermValue('searchedTerms.min_post_count', REGEXP_MIN_POST_COUNT_PREFIX);
|
||||
},
|
||||
|
||||
findSearchTerms() {
|
||||
@ -490,17 +490,17 @@ export default Em.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@observes('searchedTerms.posts_count')
|
||||
updateSearchTermForPostsCount() {
|
||||
const match = this.filterBlocks(REGEXP_POST_COUNT_PREFIX);
|
||||
const postsCountFilter = this.get('searchedTerms.posts_count');
|
||||
@observes('searchedTerms.min_post_count')
|
||||
updateSearchTermForMinPostCount() {
|
||||
const match = this.filterBlocks(REGEXP_MIN_POST_COUNT_PREFIX);
|
||||
const postsCountFilter = this.get('searchedTerms.min_post_count');
|
||||
let searchTerm = this.get('searchTerm') || '';
|
||||
|
||||
if (postsCountFilter) {
|
||||
if (match.length !== 0) {
|
||||
searchTerm = searchTerm.replace(match[0], `posts_count:${postsCountFilter}`);
|
||||
searchTerm = searchTerm.replace(match[0], `min_post_count:${postsCountFilter}`);
|
||||
} else {
|
||||
searchTerm += ` posts_count:${postsCountFilter}`;
|
||||
searchTerm += ` min_post_count:${postsCountFilter}`;
|
||||
}
|
||||
|
||||
this.set('searchTerm', searchTerm.trim());
|
||||
|
||||
@ -65,7 +65,7 @@ export default Ember.Component.extend({
|
||||
|
||||
const prevEvent = this.get('prevEvent');
|
||||
if (prevEvent) {
|
||||
this._topicScrolled(prevEvent);
|
||||
Ember.run.scheduleOnce('afterRender', this, this._topicScrolled, prevEvent);
|
||||
} else {
|
||||
Ember.run.scheduleOnce('afterRender', this, this._updateProgressBar);
|
||||
}
|
||||
|
||||
@ -27,8 +27,6 @@ export default Ember.Component.extend(bufferedRender({
|
||||
}.property('disableActions'),
|
||||
|
||||
buildBuffer(buffer) {
|
||||
const self = this;
|
||||
|
||||
const renderIcon = function(name, key, actionable) {
|
||||
const title = escapeExpression(I18n.t(`topic_statuses.${key}.help`)),
|
||||
startTag = actionable ? "a href" : "span",
|
||||
@ -39,8 +37,8 @@ export default Ember.Component.extend(bufferedRender({
|
||||
buffer.push(`<${startTag} title='${title}' class='topic-status'>${icon}</${endTag}>`);
|
||||
};
|
||||
|
||||
const renderIconIf = function(conditionProp, name, key, actionable) {
|
||||
if (!self.get(conditionProp)) { return; }
|
||||
const renderIconIf = (conditionProp, name, key, actionable) => {
|
||||
if (!this.get(conditionProp)) { return; }
|
||||
renderIcon(name, key, actionable);
|
||||
};
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ export default DiscoveryController.extend({
|
||||
return Discourse.User.currentProp('staff');
|
||||
},
|
||||
|
||||
@computed("model.categories.@each.featuredTopics.length")
|
||||
@computed("model.categories.[].featuredTopics.length")
|
||||
latestTopicOnly() {
|
||||
return this.get("model.categories").find(c => c.get("featuredTopics.length") > 1) === undefined;
|
||||
},
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
saving: false,
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.set('saving', true);
|
||||
|
||||
this.get('model').save().then(() => {
|
||||
this.transitionToRoute('group', this.get('model.name'));
|
||||
this.send('closeModal');
|
||||
}).catch(error => {
|
||||
popupAjaxError(error);
|
||||
}).finally(() => {
|
||||
this.set('saving', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,22 +1,11 @@
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import Group from 'discourse/models/group';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
loading: false,
|
||||
limit: null,
|
||||
offset: null,
|
||||
|
||||
@computed('model.owners.[]')
|
||||
isOwner(owners) {
|
||||
if (this.get('currentUser.admin')) {
|
||||
return true;
|
||||
}
|
||||
const currentUserId = this.get('currentUser.id');
|
||||
if (currentUserId) {
|
||||
return !!owners.findBy('id', currentUserId);
|
||||
}
|
||||
},
|
||||
isOwner: Ember.computed.alias('model.is_group_owner'),
|
||||
|
||||
actions: {
|
||||
removeMember(user) {
|
||||
|
||||
@ -12,7 +12,6 @@ var Tab = Em.Object.extend({
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
counts: null,
|
||||
showing: 'members',
|
||||
@ -24,12 +23,29 @@ export default Ember.Controller.extend({
|
||||
Tab.create({ name: 'messages', requiresMembership: true })
|
||||
],
|
||||
|
||||
@observes('counts')
|
||||
countsChanged() {
|
||||
const counts = this.get('counts');
|
||||
this.get('tabs').forEach(tab => {
|
||||
tab.set('count', counts.get(tab.get('name')));
|
||||
});
|
||||
@computed('model.is_group_owner', 'model.automatic')
|
||||
canEditGroup(isGroupOwner, automatic) {
|
||||
return !automatic && isGroupOwner;
|
||||
},
|
||||
|
||||
@computed('model.name', 'model.title')
|
||||
groupName(name, title) {
|
||||
return (title || name).capitalize();
|
||||
},
|
||||
|
||||
@computed('model.name', 'model.flair_url', 'model.flair_bg_color', 'model.flair_color')
|
||||
avatarFlairAttributes(groupName, flairURL, flairBgColor, flairColor) {
|
||||
return {
|
||||
primary_group_flair_url: flairURL,
|
||||
primary_group_flair_bg_color: flairBgColor,
|
||||
primary_group_flair_color: flairColor,
|
||||
primary_group_name: groupName
|
||||
};
|
||||
},
|
||||
|
||||
@observes('model.user_count')
|
||||
_setMembersTabCount() {
|
||||
this.get('tabs')[0].set('count', this.get('model.user_count'));
|
||||
},
|
||||
|
||||
@observes('showing')
|
||||
|
||||
@ -21,6 +21,9 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
if (this.site.mobileView) { this.set("viewMode", "inline"); }
|
||||
}.on("init"),
|
||||
|
||||
previousFeaturedLink: Em.computed.alias('model.featured_link_changes.previous'),
|
||||
currentFeaturedLink: Em.computed.alias('model.featured_link_changes.current'),
|
||||
|
||||
previousTagChanges: customTagArray('model.tags_changes.previous'),
|
||||
currentTagChanges: customTagArray('model.tags_changes.current'),
|
||||
|
||||
|
||||
@ -157,9 +157,7 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
|
||||
// Cook the bio for preview
|
||||
model.set('name', this.get('newNameInput'));
|
||||
var options = {};
|
||||
|
||||
return model.save(options).then(() => {
|
||||
return model.save().then(() => {
|
||||
if (Discourse.User.currentProp('id') === model.get('id')) {
|
||||
Discourse.User.currentProp('name', model.get('name'));
|
||||
}
|
||||
|
||||
@ -5,8 +5,6 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import { on, default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import Ember from 'ember';
|
||||
|
||||
const SortableArrayProxy = Ember.ArrayProxy.extend(Ember.SortableMixin);
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, {
|
||||
|
||||
@on('init')
|
||||
@ -20,12 +18,8 @@ export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, {
|
||||
return categories.map(c => bufProxy.create({ content: c }));
|
||||
},
|
||||
|
||||
categoriesOrdered: function() {
|
||||
return SortableArrayProxy.create({
|
||||
sortProperties: ['content.position'],
|
||||
content: this.get('categoriesBuffered')
|
||||
});
|
||||
}.property('categoriesBuffered'),
|
||||
categoriesSorting: ['position'],
|
||||
categoriesOrdered: Ember.computed.sort('categoriesBuffered', 'categoriesSorting'),
|
||||
|
||||
showFixIndices: function() {
|
||||
const cats = this.get('categoriesOrdered');
|
||||
|
||||
@ -160,6 +160,14 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||
return post => this.postSelected(post);
|
||||
}.property(),
|
||||
|
||||
@computed('model.isPrivateMessage', 'model.category.id')
|
||||
canEditTopicFeaturedLink(isPrivateMessage, categoryId) {
|
||||
if (!this.siteSettings.topic_featured_link_enabled || isPrivateMessage) { return false; }
|
||||
|
||||
const categoryIds = this.site.get('topic_featured_link_allowed_category_ids');
|
||||
return categoryIds === undefined || !categoryIds.length || categoryIds.indexOf(categoryId) !== -1;
|
||||
},
|
||||
|
||||
@computed('model.isPrivateMessage')
|
||||
canEditTags(isPrivateMessage) {
|
||||
return !isPrivateMessage && this.site.get('can_tag_topics');
|
||||
|
||||
@ -23,7 +23,7 @@ export default Ember.Controller.extend({
|
||||
actions: {
|
||||
exportUserArchive() {
|
||||
bootbox.confirm(
|
||||
I18n.t("user.download_archive_confirm"),
|
||||
I18n.t("user.download_archive.confirm"),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
function(confirmed) {
|
||||
|
||||
@ -18,8 +18,6 @@ export default Ember.Controller.extend({
|
||||
this.set('searchTerm', '');
|
||||
},
|
||||
|
||||
uploadText: function() { return I18n.t("user.invited.bulk_invite.text"); }.property(),
|
||||
|
||||
/**
|
||||
Observe the search term box with a debouncer and change the results.
|
||||
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { registerUnbound } from 'discourse-common/lib/helpers';
|
||||
import renderTopicFeaturedLink from 'discourse/lib/render-topic-featured-link';
|
||||
|
||||
export default registerUnbound('topic-featured-link', function(topic, params) {
|
||||
return new Handlebars.SafeString(renderTopicFeaturedLink(topic, params));
|
||||
});
|
||||
@ -8,9 +8,9 @@ function exportEntityByType(type, entity, args) {
|
||||
|
||||
export function exportUserArchive() {
|
||||
return exportEntityByType('user', 'user_archive').then(function() {
|
||||
bootbox.alert(I18n.t("admin.export_csv.success"));
|
||||
bootbox.alert(I18n.t("user.download_archive.success"));
|
||||
}).catch(function() {
|
||||
bootbox.alert(I18n.t("admin.export_csv.rate_limit_error"));
|
||||
bootbox.alert(I18n.t("user.download_archive.rate_limit_error"));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -21,10 +21,11 @@ export default function interceptClick(e) {
|
||||
$currentTarget.data('ember-action') ||
|
||||
$currentTarget.data('auto-route') ||
|
||||
$currentTarget.data('share-url') ||
|
||||
$currentTarget.data('user-card') ||
|
||||
$currentTarget.hasClass('widget-link') ||
|
||||
$currentTarget.hasClass('mention') ||
|
||||
(!$currentTarget.hasClass('d-link') && $currentTarget.hasClass('ember-view')) ||
|
||||
(!$currentTarget.hasClass('d-link') &&
|
||||
!$currentTarget.data('user-card') &&
|
||||
$currentTarget.hasClass('ember-view')) ||
|
||||
$currentTarget.hasClass('lightbox') ||
|
||||
href.indexOf("mailto:") === 0 ||
|
||||
(href.match(/^http[s]?:\/\//i) && !href.match(new RegExp("^https?:\\/\\/" + window.location.hostname, "i")))) {
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
import { extractDomainFromUrl } from 'discourse/lib/utilities';
|
||||
import { h } from 'virtual-dom';
|
||||
|
||||
const _decorators = [];
|
||||
|
||||
export function addFeaturedLinkMetaDecorator(decorator) {
|
||||
_decorators.push(decorator);
|
||||
}
|
||||
|
||||
function extractLinkMeta(topic) {
|
||||
const href = topic.featured_link, target = Discourse.SiteSettings.open_topic_featured_link_in_external_window ? '_blank' : '';
|
||||
if (!href) { return; }
|
||||
|
||||
let domain = extractDomainFromUrl(href);
|
||||
if (!domain) { return; }
|
||||
|
||||
// www appears frequently, so we truncate it
|
||||
if (domain && domain.substr(0, 4) === 'www.') {
|
||||
domain = domain.substring(4);
|
||||
}
|
||||
|
||||
const meta = { target, href, domain, rel: 'nofollow' };
|
||||
if (_decorators.length) {
|
||||
_decorators.forEach(cb => cb(meta));
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
export default function renderTopicFeaturedLink(topic) {
|
||||
const meta = extractLinkMeta(topic);
|
||||
if (meta) {
|
||||
return `<a class="topic-featured-link" rel="${meta.rel}" target="${meta.target}" href="${meta.href}">${meta.domain}</a>`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export function topicFeaturedLinkNode(topic) {
|
||||
const meta = extractLinkMeta(topic);
|
||||
if (meta) {
|
||||
return h('a.topic-featured-link', {
|
||||
attributes: { href: meta.href, rel: meta.rel, target: meta.target }
|
||||
}, meta.domain);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,8 @@ function applicable() {
|
||||
}
|
||||
|
||||
let workaroundActive = false;
|
||||
let composingTopic = false;
|
||||
|
||||
export function isWorkaroundActive() {
|
||||
return workaroundActive;
|
||||
}
|
||||
@ -22,27 +24,38 @@ function positioningWorkaround($fixedElement) {
|
||||
var done = false;
|
||||
var originalScrollTop = 0;
|
||||
|
||||
positioningWorkaround.blur = function(evt) {
|
||||
if (workaroundActive) {
|
||||
done = true;
|
||||
|
||||
$('#main-outlet').show();
|
||||
$('header').show();
|
||||
|
||||
fixedElement.style.position = '';
|
||||
fixedElement.style.top = '';
|
||||
fixedElement.style.height = '';
|
||||
|
||||
$(window).scrollTop(originalScrollTop);
|
||||
|
||||
if (evt) {
|
||||
evt.target.removeEventListener('blur', blurred);
|
||||
}
|
||||
workaroundActive = false;
|
||||
}
|
||||
};
|
||||
|
||||
var blurredNow = function(evt) {
|
||||
if (!done && _.include($(document.activeElement).parents(), fixedElement)) {
|
||||
// something in focus so skip
|
||||
return;
|
||||
}
|
||||
|
||||
done = true;
|
||||
|
||||
$('#main-outlet').show();
|
||||
$('header').show();
|
||||
|
||||
fixedElement.style.position = '';
|
||||
fixedElement.style.top = '';
|
||||
fixedElement.style.height = '';
|
||||
|
||||
$(window).scrollTop(originalScrollTop);
|
||||
|
||||
if (evt) {
|
||||
evt.target.removeEventListener('blur', blurred);
|
||||
if (composingTopic) {
|
||||
return false;
|
||||
}
|
||||
workaroundActive = false;
|
||||
|
||||
positioningWorkaround.blur(evt);
|
||||
|
||||
};
|
||||
|
||||
var blurred = _.debounce(blurredNow, 250);
|
||||
@ -73,7 +86,20 @@ function positioningWorkaround($fixedElement) {
|
||||
|
||||
fixedElement.style.top = '0px';
|
||||
|
||||
const height = Math.max(parseInt(window.innerHeight*0.6), 350);
|
||||
let ratio = 0.6;
|
||||
let min = 350;
|
||||
|
||||
composingTopic = false;
|
||||
|
||||
if ($('#reply-control select.category-combobox').length > 0) {
|
||||
composingTopic = true;
|
||||
// creating a topic, less height
|
||||
ratio = 0.54;
|
||||
min = 300;
|
||||
}
|
||||
|
||||
const height = Math.max(parseInt(window.innerHeight*ratio), min);
|
||||
|
||||
fixedElement.style.height = height + "px";
|
||||
|
||||
// I used to do this, but it seems like we don't need to with position
|
||||
|
||||
@ -234,7 +234,7 @@ export function uploadLocation(url) {
|
||||
} else {
|
||||
var protocol = window.location.protocol + '//',
|
||||
hostname = window.location.hostname,
|
||||
port = ':' + window.location.port;
|
||||
port = window.location.port ? ':' + window.location.port : '';
|
||||
return protocol + hostname + port + url;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,11 +5,94 @@ const BareRouter = Ember.Router.extend({
|
||||
location: Ember.testing ? 'none': 'discourse-location'
|
||||
});
|
||||
|
||||
export function mapRoutes() {
|
||||
// Ember's router can't be extended. We need to allow plugins to add routes to routes that were defined
|
||||
// in the core app. This class has the same API as Ember's `Router.map` but saves the results in a tree.
|
||||
// The tree is applied after all plugins are defined.
|
||||
class RouteNode {
|
||||
constructor(name, opts={}, depth=0) {
|
||||
this.name = name;
|
||||
this.opts = opts;
|
||||
this.depth = depth;
|
||||
this.children = [];
|
||||
this.childrenByName = {};
|
||||
this.paths = {};
|
||||
|
||||
var Router = BareRouter.extend();
|
||||
const resources = {};
|
||||
const paths = {};
|
||||
if (!opts.path) {
|
||||
opts.path = name;
|
||||
}
|
||||
|
||||
this.paths[opts.path] = true;
|
||||
}
|
||||
|
||||
route(name, opts, fn) {
|
||||
if (typeof opts === 'function') {
|
||||
fn = opts;
|
||||
opts = {};
|
||||
} else {
|
||||
opts = opts || {};
|
||||
}
|
||||
|
||||
const existing = this.childrenByName[name];
|
||||
if (existing) {
|
||||
if (opts.path) {
|
||||
existing.paths[opts.path] = true;
|
||||
}
|
||||
existing.extract(fn);
|
||||
} else {
|
||||
const node = new RouteNode(name, opts, this.depth+1);
|
||||
node.extract(fn);
|
||||
this.childrenByName[name] = node;
|
||||
this.children.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
extract(fn) {
|
||||
if (!fn) { return; }
|
||||
fn.call(this);
|
||||
}
|
||||
|
||||
mapRoutes(router) {
|
||||
const children = this.children;
|
||||
if (this.name === 'root') {
|
||||
children.forEach(c => c.mapRoutes(router));
|
||||
} else {
|
||||
|
||||
const builder = (children.length === 0) ? undefined : function() {
|
||||
children.forEach(c => c.mapRoutes(this));
|
||||
};
|
||||
router.route(this.name, this.opts, builder);
|
||||
|
||||
// We can have multiple paths to the same route
|
||||
const paths = Object.keys(this.paths);
|
||||
if (paths.length > 1) {
|
||||
paths.filter(p => p !== this.opts.path).forEach(path => {
|
||||
const newOpts = jQuery.extend({}, this.opts, { path });
|
||||
router.route(this.name, newOpts, builder);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findSegment(segments) {
|
||||
if (segments && segments.length) {
|
||||
const first = segments.shift();
|
||||
const node = this.childrenByName[first];
|
||||
if (node) {
|
||||
return (segments.length === 0) ? node : node.findSegment(segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findPath(path) {
|
||||
if (path) {
|
||||
return this.findSegment(path.split('.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function mapRoutes() {
|
||||
const tree = new RouteNode('root');
|
||||
const extras = [];
|
||||
|
||||
// If a module is defined as `route-map` in discourse or a plugin, its routes
|
||||
// will be built automatically. You can supply a `resource` property to
|
||||
@ -20,62 +103,24 @@ export function mapRoutes() {
|
||||
var module = require(key, null, null, true);
|
||||
if (!module || !module.default) { throw new Error(key + ' must export a route map.'); }
|
||||
|
||||
var mapObj = module.default;
|
||||
const mapObj = module.default;
|
||||
if (typeof mapObj === 'function') {
|
||||
mapObj = { resource: 'root', map: mapObj };
|
||||
tree.extract(mapObj);
|
||||
} else {
|
||||
extras.push(mapObj);
|
||||
}
|
||||
|
||||
if (!resources[mapObj.resource]) { resources[mapObj.resource] = []; }
|
||||
resources[mapObj.resource].push(mapObj.map);
|
||||
if (mapObj.path) { paths[mapObj.resource] = mapObj.path; }
|
||||
}
|
||||
});
|
||||
|
||||
return Router.map(function() {
|
||||
var router = this;
|
||||
|
||||
// Do the root resources first
|
||||
if (resources.root) {
|
||||
resources.root.forEach(function(m) {
|
||||
m.call(router);
|
||||
});
|
||||
delete resources.root;
|
||||
extras.forEach(extra => {
|
||||
const node = tree.findPath(extra.resource);
|
||||
if (node) {
|
||||
node.extract(extra.map);
|
||||
}
|
||||
});
|
||||
|
||||
// Even if no plugins set it up, we need an `adminPlugins` route
|
||||
var adminPlugins = 'admin.adminPlugins';
|
||||
resources[adminPlugins] = resources[adminPlugins] || [Ember.K];
|
||||
paths[adminPlugins] = paths[adminPlugins] || "/plugins";
|
||||
|
||||
var segments = {},
|
||||
standalone = [];
|
||||
|
||||
Object.keys(resources).forEach(function(r) {
|
||||
var m = /^([^\.]+)\.(.*)$/.exec(r);
|
||||
if (m) {
|
||||
segments[m[1]] = m[2];
|
||||
} else {
|
||||
standalone.push(r);
|
||||
}
|
||||
});
|
||||
|
||||
// Apply other resources next. A little hacky but works!
|
||||
standalone.forEach(function(r) {
|
||||
router.route(r, {path: paths[r], resetNamespace: true}, function() {
|
||||
var res = this;
|
||||
resources[r].forEach(function(m) { m.call(res); });
|
||||
|
||||
var s = segments[r];
|
||||
if (s) {
|
||||
var full = r + '.' + s;
|
||||
res.route(s, {path: paths[full], resetNamespace: true}, function() {
|
||||
var nestedRes = this;
|
||||
resources[full].forEach(function(m) { m.call(nestedRes); });
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return BareRouter.extend().map(function() {
|
||||
tree.mapRoutes(this);
|
||||
this.route('unknown', {path: '*path'});
|
||||
});
|
||||
}
|
||||
|
||||
@ -169,6 +169,18 @@ const Category = RestModel.extend({
|
||||
@computed("id")
|
||||
isUncategorizedCategory(id) {
|
||||
return id === Discourse.Site.currentProp("uncategorized_category_id");
|
||||
},
|
||||
|
||||
@computed('custom_fields.topic_featured_link_allowed')
|
||||
topicFeaturedLinkAllowed: {
|
||||
get(allowed) {
|
||||
return allowed === "true";
|
||||
},
|
||||
set(value) {
|
||||
value = value ? "true" : "false";
|
||||
this.set("custom_fields.topic_featured_link_allowed", value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -32,13 +32,15 @@ const CLOSED = 'closed',
|
||||
target_usernames: 'targetUsernames',
|
||||
typing_duration_msecs: 'typingTime',
|
||||
composer_open_duration_msecs: 'composerTime',
|
||||
tags: 'tags'
|
||||
tags: 'tags',
|
||||
featured_link: 'featuredLink'
|
||||
},
|
||||
|
||||
_edit_topic_serializer = {
|
||||
title: 'topic.title',
|
||||
categoryId: 'topic.category.id',
|
||||
tags: 'topic.tags'
|
||||
tags: 'topic.tags',
|
||||
featuredLink: 'topic.featured_link'
|
||||
};
|
||||
|
||||
const Composer = RestModel.extend({
|
||||
@ -136,6 +138,14 @@ const Composer = RestModel.extend({
|
||||
canEditTitle: Em.computed.or('creatingTopic', 'creatingPrivateMessage', 'editingFirstPost'),
|
||||
canCategorize: Em.computed.and('canEditTitle', 'notCreatingPrivateMessage'),
|
||||
|
||||
@computed('canEditTitle', 'creatingPrivateMessage', 'categoryId')
|
||||
canEditTopicFeaturedLink(canEditTitle, creatingPrivateMessage, categoryId) {
|
||||
if (!this.siteSettings.topic_featured_link_enabled || !canEditTitle || creatingPrivateMessage) { return false; }
|
||||
|
||||
const categoryIds = this.site.get('topic_featured_link_allowed_category_ids');
|
||||
return categoryIds === undefined || !categoryIds.length || categoryIds.indexOf(categoryId) !== -1;
|
||||
},
|
||||
|
||||
// Determine the appropriate title for this action
|
||||
actionTitle: function() {
|
||||
const topic = this.get('topic');
|
||||
@ -180,6 +190,10 @@ const Composer = RestModel.extend({
|
||||
|
||||
}.property('action', 'post', 'topic', 'topic.title'),
|
||||
|
||||
@computed('canEditTopicFeaturedLink')
|
||||
showComposerEditor(canEditTopicFeaturedLink) {
|
||||
return canEditTopicFeaturedLink ? !this.siteSettings.topic_featured_link_onebox : true;
|
||||
},
|
||||
|
||||
// whether to disable the post button
|
||||
cantSubmitPost: function() {
|
||||
@ -269,11 +283,12 @@ const Composer = RestModel.extend({
|
||||
}
|
||||
}.property('privateMessage'),
|
||||
|
||||
missingReplyCharacters: function() {
|
||||
const postType = this.get('post.post_type');
|
||||
if (postType === this.site.get('post_types.small_action')) { return 0; }
|
||||
return this.get('minimumPostLength') - this.get('replyLength');
|
||||
}.property('minimumPostLength', 'replyLength'),
|
||||
@computed('minimumPostLength', 'replyLength', 'canEditTopicFeaturedLink')
|
||||
missingReplyCharacters(minimumPostLength, replyLength, canEditTopicFeaturedLink) {
|
||||
if (this.get('post.post_type') === this.site.get('post_types.small_action') ||
|
||||
canEditTopicFeaturedLink && this.siteSettings.topic_featured_link_onebox) { return 0; }
|
||||
return minimumPostLength - replyLength;
|
||||
},
|
||||
|
||||
/**
|
||||
Minimum number of characters for a post body to be valid.
|
||||
@ -492,6 +507,14 @@ const Composer = RestModel.extend({
|
||||
|
||||
save(opts) {
|
||||
if (!this.get('cantSubmitPost')) {
|
||||
|
||||
// change category may result in some effect for topic featured link
|
||||
if (this.get('canEditTopicFeaturedLink')) {
|
||||
if (this.siteSettings.topic_featured_link_onebox) { this.set('reply', null); }
|
||||
} else {
|
||||
this.set('featuredLink', null);
|
||||
}
|
||||
|
||||
return this.get('editingPost') ? this.editPost(opts) : this.createPost(opts);
|
||||
}
|
||||
},
|
||||
@ -512,7 +535,8 @@ const Composer = RestModel.extend({
|
||||
stagedPost: false,
|
||||
typingTime: 0,
|
||||
composerOpened: null,
|
||||
composerTotalOpened: 0
|
||||
composerTotalOpened: 0,
|
||||
featuredLink: null
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -114,18 +114,25 @@ const Group = Discourse.Model.extend({
|
||||
flair_url: this.get('flair_url'),
|
||||
flair_bg_color: this.get('flairBackgroundHexColor'),
|
||||
flair_color: this.get('flairHexColor'),
|
||||
bio_raw: this.get('bio_raw')
|
||||
};
|
||||
},
|
||||
|
||||
create() {
|
||||
var self = this;
|
||||
return ajax("/admin/groups", { type: "POST", data: this.asJSON() }).then(function(resp) {
|
||||
return ajax("/admin/groups", { type: "POST", data: { group: this.asJSON() } }).then(function(resp) {
|
||||
self.set('id', resp.basic_group.id);
|
||||
});
|
||||
},
|
||||
|
||||
save() {
|
||||
return ajax("/admin/groups/" + this.get('id'), { type: "PUT", data: this.asJSON() });
|
||||
const id = this.get('id');
|
||||
const url = this.get('is_group_owner') ? `/groups/${id}` : `/admin/groups/${id}`;
|
||||
|
||||
return ajax(url, {
|
||||
type: "PUT",
|
||||
data: { group: this.asJSON() }
|
||||
});
|
||||
},
|
||||
|
||||
destroy() {
|
||||
@ -166,10 +173,6 @@ Group.reopenClass({
|
||||
});
|
||||
},
|
||||
|
||||
findGroupCounts(name) {
|
||||
return ajax("/groups/" + name + "/counts.json").then(result => Em.Object.create(result.counts));
|
||||
},
|
||||
|
||||
find(name) {
|
||||
return ajax("/groups/" + name + ".json").then(result => Group.create(result.basic_group));
|
||||
},
|
||||
|
||||
@ -28,11 +28,21 @@ export default RestModel.extend({
|
||||
|
||||
baseUrl: url('itemsLoaded', 'user.username_lower', '/user_actions.json?offset=%@&username=%@'),
|
||||
|
||||
filterBy(filter) {
|
||||
this.setProperties({ filter, itemsLoaded: 0, content: [], lastLoadedUrl: null });
|
||||
filterBy(filter, noContentHelpKey) {
|
||||
this.setProperties({
|
||||
filter,
|
||||
itemsLoaded: 0,
|
||||
content: [],
|
||||
noContentHelpKey: noContentHelpKey,
|
||||
lastLoadedUrl: null
|
||||
});
|
||||
return this.findItems();
|
||||
},
|
||||
|
||||
noContent: function() {
|
||||
return this.get('loaded') && this.get('content').length === 0;
|
||||
}.property('loaded', 'content.@each'),
|
||||
|
||||
remove(userAction) {
|
||||
// 1) remove the user action from the child groups
|
||||
this.get("content").forEach(function (ua) {
|
||||
@ -61,6 +71,9 @@ export default RestModel.extend({
|
||||
if (this.get('filterParam')) {
|
||||
findUrl += "&filter=" + this.get('filterParam');
|
||||
}
|
||||
if (this.get('noContentHelpKey')) {
|
||||
findUrl += "&no_results_help_key=" + this.get('noContentHelpKey');
|
||||
}
|
||||
|
||||
// Don't load the same stream twice. We're probably at the end.
|
||||
const lastLoadedUrl = this.get('lastLoadedUrl');
|
||||
@ -69,6 +82,9 @@ export default RestModel.extend({
|
||||
if (this.get('loading')) { return Ember.RSVP.resolve(); }
|
||||
this.set('loading', true);
|
||||
return ajax(findUrl, {cache: 'false'}).then( function(result) {
|
||||
if (result && result.no_results_help) {
|
||||
self.set('noContentHelp', result.no_results_help);
|
||||
}
|
||||
if (result && result.user_actions) {
|
||||
const copy = Em.A();
|
||||
result.user_actions.forEach(function(action) {
|
||||
@ -78,11 +94,11 @@ export default RestModel.extend({
|
||||
|
||||
self.get('content').pushObjects(UserAction.collapseStream(copy));
|
||||
self.setProperties({
|
||||
loaded: true,
|
||||
itemsLoaded: self.get('itemsLoaded') + result.user_actions.length
|
||||
});
|
||||
}
|
||||
}).finally(function() {
|
||||
self.set('loaded', true);
|
||||
self.set('loading', false);
|
||||
self.set('lastLoadedUrl', findUrl);
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/*global Modernizr:true*/
|
||||
/*global Modernizr:true safari:true*/
|
||||
|
||||
// Initializes an object that lets us know about our capabilities.
|
||||
export default {
|
||||
@ -20,7 +20,7 @@ export default {
|
||||
|
||||
caps.isOpera = !!window.opera || ua.indexOf(' OPR/') >= 0;
|
||||
caps.isFirefox = typeof InstallTrigger !== 'undefined';
|
||||
caps.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
|
||||
caps.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || safari.pushNotification);
|
||||
caps.isChrome = !!window.chrome && !caps.isOpera;
|
||||
caps.canPasteImages = caps.isChrome || caps.isFirefox;
|
||||
}
|
||||
|
||||
@ -132,7 +132,7 @@ export default function() {
|
||||
this.route('showCategory' + filter.capitalize(), {path: '/c/:category/:tag_id/l/' + filter});
|
||||
this.route('showParentCategory' + filter.capitalize(), {path: '/c/:parent_category/:category/:tag_id/l/' + filter});
|
||||
});
|
||||
this.route('show', {path: 'intersection/:tag_id/*additional_tags'});
|
||||
this.route('intersection', {path: 'intersection/:tag_id/*additional_tags'});
|
||||
});
|
||||
|
||||
this.route('tagGroups', {path: '/tag_groups', resetNamespace: true}, function() {
|
||||
|
||||
@ -146,10 +146,9 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
|
||||
},
|
||||
|
||||
changeBulkTemplate(w) {
|
||||
const controllerName = w.replace('modal/', ''),
|
||||
factory = getOwner(this).lookupFactory('controller:' + controllerName);
|
||||
|
||||
this.render(w, {into: 'modal/topic-bulk-actions', outlet: 'bulkOutlet', controller: factory ? controllerName : 'topic-bulk-actions'});
|
||||
const controllerName = w.replace('modal/', '');
|
||||
const controller = getOwner(this).lookup('controller:' + controllerName);
|
||||
this.render(w, {into: 'modal/topic-bulk-actions', outlet: 'bulkOutlet', controller: controller ? controllerName : 'topic-bulk-actions'});
|
||||
},
|
||||
|
||||
createNewTopicViaParams(title, body, category_id, category, tags) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import Group from 'discourse/models/group';
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
|
||||
@ -14,13 +15,14 @@ export default Discourse.Route.extend({
|
||||
return { name: model.get('name').toLowerCase() };
|
||||
},
|
||||
|
||||
afterModel(model) {
|
||||
return Group.findGroupCounts(model.get('name')).then(counts => {
|
||||
this.set('counts', counts);
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.setProperties({ model, counts: this.get('counts') });
|
||||
},
|
||||
|
||||
actions: {
|
||||
showGroupEditor() {
|
||||
showModal('edit-group');
|
||||
this.controllerFor('edit-group').set('model', this.modelFor('group'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import TagsShowRoute from 'discourse/routes/tags-show';
|
||||
|
||||
export default TagsShowRoute.extend({});
|
||||
|
||||
// The tags-intersection route is exactly the same as the tags-show route, but the wildcard at the
|
||||
// end of the route (*additional_tags) will cause a match when query parameters are present,
|
||||
// breaking all other tags-show routes. Ember thinks the query params are addition tags and should
|
||||
// be handled by the intersection logic. Defining tags-intersection as something separate avoids
|
||||
// that confusion.
|
||||
@ -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["bookmarks"]
|
||||
userActionType: UserAction.TYPES["bookmarks"],
|
||||
noContentHelpKey: "user_activity.no_bookmarks"
|
||||
});
|
||||
|
||||
@ -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["likes_given"]
|
||||
userActionType: UserAction.TYPES["likes_given"],
|
||||
noContentHelpKey: 'user_activity.no_likes_given'
|
||||
});
|
||||
|
||||
@ -6,7 +6,7 @@ export default Discourse.Route.extend(ViewingActionType, {
|
||||
},
|
||||
|
||||
afterModel() {
|
||||
return this.modelFor("user").get("stream").filterBy(this.get("userActionType"));
|
||||
return this.modelFor("user").get("stream").filterBy(this.get("userActionType"), this.get("noContentHelpKey"));
|
||||
},
|
||||
|
||||
renderTemplate() {
|
||||
|
||||
@ -33,14 +33,6 @@ export default Discourse.Route.extend({
|
||||
showInvite() {
|
||||
showModal("invite", { model: this.currentUser });
|
||||
this.controllerFor("invite").reset();
|
||||
},
|
||||
|
||||
uploadSuccess(filename) {
|
||||
bootbox.alert(I18n.t("user.invited.bulk_invite.success", { filename: filename }));
|
||||
},
|
||||
|
||||
uploadError(filename, message) {
|
||||
bootbox.alert(I18n.t("user.invited.bulk_invite.error", { filename: filename, message: message }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
<label class="btn" disabled={{uploading}}>
|
||||
{{fa-icon "upload"}} {{uploadButtonText}}
|
||||
<input disabled={{uploading}} type="file" accept=".csv" style="visibility: hidden; position: absolute;" />
|
||||
</label>
|
||||
{{#if uploading}}
|
||||
<span>{{i18n 'upload_selector.uploading'}} {{uploadProgress}}%</span>
|
||||
{{/if}}
|
||||
@ -19,6 +19,17 @@
|
||||
</label>
|
||||
</section>
|
||||
|
||||
{{#if siteSettings.topic_featured_link_enabled}}
|
||||
<section class='field'>
|
||||
<div class="allowed-topic-featured-link-category">
|
||||
<label class="checkbox-label">
|
||||
{{input type="checkbox" checked=category.topicFeaturedLinkAllowed}}
|
||||
{{i18n 'category.topic_featured_link_allowed'}}
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<section class="field">
|
||||
<label>
|
||||
{{i18n "category.sort_order"}}
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
<div class="group-flair-left">
|
||||
<div>
|
||||
<label for="flair_url">{{i18n 'group.flair_url'}}</label>
|
||||
{{text-field name="flair_url"
|
||||
value=model.flair_url
|
||||
placeholderKey="group.flair_url_placeholder"}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="flair_bg_color">{{i18n 'group.flair_bg_color'}}</label>
|
||||
{{text-field name="flair_bg_color"
|
||||
class="group-flair-bg-color"
|
||||
value=model.flair_bg_color
|
||||
placeholderKey="group.flair_bg_color_placeholder"}}
|
||||
</div>
|
||||
|
||||
{{#if flairPreviewIcon}}
|
||||
<div>
|
||||
<label for="flair_color">{{i18n 'group.flair_color'}}</label>
|
||||
{{text-field name="flair_color"
|
||||
class="group-flair-color"
|
||||
value=model.flair_color
|
||||
placeholderKey="group.flair_color_placeholder"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div>
|
||||
<strong>{{i18n 'group.flair_note'}}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group-flair-right">
|
||||
<label>{{flairPreviewLabel}}</label>
|
||||
<div class="avatar-flair-preview">
|
||||
<div class="avatar-wrapper">
|
||||
<img width="45" height="45" src="{{demoAvatarUrl}}" class="avatar actor">
|
||||
</div>
|
||||
|
||||
{{#if flairPreviewImage}}
|
||||
<div class="avatar-flair demo {{flairPreviewClasses}}" style={{flairPreviewStyle}}></div>
|
||||
{{else}}
|
||||
<div class="avatar-flair demo {{flairPreviewClasses}}" style={{flairPreviewStyle}}>
|
||||
<i class="fa {{model.flair_url}}"></i>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@ -7,3 +7,5 @@
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/load-more}}
|
||||
|
||||
{{conditional-loading-spinner condition=loading}}
|
||||
|
||||
@ -7,12 +7,12 @@
|
||||
</span>
|
||||
<span class="category">{{category-link post.category}}</span>
|
||||
<div class="group-member-info">
|
||||
{{#if post.user_long_name}}
|
||||
<span class="name">{{post.user_long_name}}</span>{{#if post.user_title}}<span class="title">, {{post.user_title}}</span>{{/if}}
|
||||
{{#if post.user.name}}
|
||||
<span class="name">{{post.user.name}}</span>{{#if post.user.title}}<span class="title">, {{post.user.title}}</span>{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<p class='excerpt'>
|
||||
{{{unbound post.cooked}}}
|
||||
{{{unbound post.excerpt}}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -12,6 +12,9 @@
|
||||
<tr>
|
||||
{{topic-status topic=topic}}
|
||||
{{topic-link topic}}
|
||||
{{#if topic.featured_link}}
|
||||
{{topic-featured-link topic}}
|
||||
{{/if}}
|
||||
{{topic-post-badges newPosts=topic.totalUnread unseen=topic.unseen url=topic.lastUnreadUrl}}
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@ -75,9 +75,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group pull-left">
|
||||
<label class="control-label" for="search-posts-count">{{i18n "search.advanced.post.count.label"}}</label>
|
||||
<label class="control-label" for="search-min-post-count">{{i18n "search.advanced.post.count.label"}}</label>
|
||||
<div class="controls">
|
||||
{{input type="number" value=searchedTerms.posts_count class="input-small" id='search-posts-count'}}
|
||||
{{input type="number" value=searchedTerms.min_post_count class="input-small" id='search-min-post-count'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -2,12 +2,16 @@
|
||||
{{bound-category-link topic.category.parentCategory}}
|
||||
{{/if}}
|
||||
{{bound-category-link topic.category hideParent=true}}
|
||||
{{#if siteSettings.tagging_enabled}}
|
||||
<div class="list-tags">
|
||||
{{#each topic.tags as |t|}}
|
||||
{{discourse-tag t}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="topic-header-extra">
|
||||
{{#if siteSettings.tagging_enabled}}
|
||||
<div class="list-tags">
|
||||
{{#each topic.tags as |t|}}
|
||||
{{discourse-tag t}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if siteSettings.topic_featured_link_enabled}}
|
||||
{{topic-featured-link topic}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{plugin-outlet "topic-category"}}
|
||||
|
||||
@ -4,7 +4,11 @@
|
||||
<div class="user-card-avatar">
|
||||
<a href={{user.path}} {{action "showUser"}} class="card-huge-avatar">{{bound-avatar avatar "huge"}}</a>
|
||||
{{#if user.primary_group_name}}
|
||||
{{mount-widget widget="avatar-flair" args=user}}
|
||||
{{avatar-flair
|
||||
flairURL=user.primary_group_flair_url
|
||||
flairBgColor=user.primary_group_flair_bg_color
|
||||
flairColor=user.primary_group_flair_color
|
||||
groupName=user.primary_group_name}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
|
||||
@ -80,9 +80,13 @@
|
||||
{{/if}}
|
||||
{{render "additional-composer-buttons" model}}
|
||||
{{/if}}
|
||||
{{#if model.canEditTopicFeaturedLink}}
|
||||
<div class="topic-featured-link-input">
|
||||
{{text-field tabindex="4" type="url" value=model.featuredLink id='topic-featured-link' placeholderKey="composer.topic_featured_link_placeholder"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{plugin-outlet "composer-fields"}}
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,45 +1,48 @@
|
||||
{{#if model}}
|
||||
{{#if isOwner}}
|
||||
<div class='clearfix'>
|
||||
<form id='add-user-to-group' autocomplete="off">
|
||||
{{user-selector usernames=usernames placeholderKey="groups.selector_placeholder" id="user-search-selector" name="usernames"}}
|
||||
{{d-button action="addMembers" class="add" icon="plus" label="groups.add"}}
|
||||
</form>
|
||||
</div>
|
||||
<form id='add-user-to-group' autocomplete="off">
|
||||
{{user-selector usernames=usernames placeholderKey="groups.selector_placeholder" id="user-search-selector" name="usernames"}}
|
||||
{{d-button action="addMembers" class="add" icon="plus" label="groups.add"}}
|
||||
</form>
|
||||
{{/if}}
|
||||
|
||||
{{#load-more selector=".group-members tr" action="loadMore"}}
|
||||
<table class='group-members'>
|
||||
<tr>
|
||||
<th colspan="2">{{i18n 'last_post'}}</th>
|
||||
<th>{{i18n 'last_seen'}}</th>
|
||||
{{#if isOwner}}
|
||||
<thead>
|
||||
<th></th>
|
||||
{{/if}}
|
||||
</tr>
|
||||
{{#each model.members as |m|}}
|
||||
<tr>
|
||||
<td class='avatar'>
|
||||
{{user-info user=m}}
|
||||
{{#if m.owner}}<span class='is-owner'>{{i18n "groups.owner"}}</span>{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
<span class="text">{{bound-date m.last_posted_at}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text">{{bound-date m.last_seen_at}}</span>
|
||||
</td>
|
||||
{{#if isOwner}}
|
||||
<td class='remove-user'>
|
||||
{{#unless m.owner}}
|
||||
<a class="remove-link" {{action "removeMember" m}}><i class="fa fa-times"></i></a>
|
||||
{{/unless}}
|
||||
<th>{{i18n 'last_post'}}</th>
|
||||
<th>{{i18n 'last_seen'}}</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#each model.members as |m|}}
|
||||
<tr>
|
||||
<td class='avatar'>
|
||||
{{#user-info user=m skipName=skipName}}
|
||||
{{#if m.owner}}<strong class="group-owner-label">{{i18n "groups.owner"}}</strong>{{/if}}
|
||||
{{/user-info}}
|
||||
</td>
|
||||
{{/if}}
|
||||
</tr>
|
||||
{{/each}}
|
||||
<td>
|
||||
<span class="text">{{bound-date m.last_posted_at}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text">{{bound-date m.last_seen_at}}</span>
|
||||
</td>
|
||||
<td class='remove-user'>
|
||||
{{#if isOwner}}
|
||||
{{#unless m.owner}}
|
||||
<a class="remove-link" {{action "removeMember" m}}><i class="fa fa-times"></i></a>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{/load-more}}
|
||||
|
||||
{{conditional-loading-spinner condition=loading}}
|
||||
{{else}}
|
||||
<div>{{i18n "groups.empty.users"}}</div>
|
||||
{{/if}}
|
||||
|
||||
@ -1 +1 @@
|
||||
{{group-post-stream posts=model emptyText=emptyText loadMore="loadMore"}}
|
||||
{{group-post-stream posts=model emptyText=emptyText loadMore="loadMore" loading=loading}}
|
||||
|
||||
@ -1,27 +1,56 @@
|
||||
<div class="container user-table">
|
||||
<div class="wrapper">
|
||||
<section class='user-navigation'>
|
||||
<ul class='action-list nav-stacked'>
|
||||
{{#each getTabs as |tab|}}
|
||||
<li class="{{if tab.active 'active'}}">
|
||||
{{#link-to tab.location model title=tab.message}}
|
||||
{{tab.message}}
|
||||
{{#if tab.count}}<span class='count'>({{tab.count}})</span>{{/if}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</section>
|
||||
<div class="container group">
|
||||
<div class='group-details-container'>
|
||||
<div class='group-details'>
|
||||
{{#if model.flair_url}}
|
||||
<span class='group-avatar-flair'>
|
||||
{{avatar-flair
|
||||
flairURL=model.flair_url
|
||||
flairBgColor=model.flair_bg_color
|
||||
flairColor=model.flair_color
|
||||
groupName=model.name}}
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
<section class='user-main'>
|
||||
<section class='user-right groups'>
|
||||
<section class='about group'>
|
||||
<div class='details'>
|
||||
<h1>{{model.name}}</h1>
|
||||
</div>
|
||||
</section>
|
||||
<span>
|
||||
<h1 class='group-header'>
|
||||
<span class='group-title'>{{groupName}}</span>
|
||||
</h1>
|
||||
|
||||
{{#if model.title}}
|
||||
<h3 class='group-name'>@{{model.name}}</h3>
|
||||
{{/if}}
|
||||
</span>
|
||||
|
||||
{{#if canEditGroup}}
|
||||
<span class="group-edit">
|
||||
{{d-button action="showGroupEditor" label="group.edit.title" class="group-edit-btn" icon="pencil"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if model.bio_cooked}}
|
||||
<hr/>
|
||||
|
||||
<div class='group-bio'>
|
||||
<p>{{{model.bio_cooked}}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#mobile-nav class='group-nav' desktopClass="pull-left nav nav-stacked" currentPath=currentPath}}
|
||||
{{#each getTabs as |tab|}}
|
||||
<li class="{{if tab.active 'active'}}">
|
||||
{{#link-to tab.location model title=tab.message}}
|
||||
{{tab.message}}
|
||||
{{#if tab.count}}<span class='count'>({{tab.count}})</span>{{/if}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/each}}
|
||||
{{/mobile-nav}}
|
||||
|
||||
<div class='pull-left group-outlet'>
|
||||
<div class='user-main'>
|
||||
{{outlet}}
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -7,6 +7,9 @@
|
||||
<td class='main-link clearfix' colspan="{{titleColSpan}}">
|
||||
{{raw "topic-status" topic=topic}}
|
||||
{{topic-link topic}}
|
||||
{{#if topic.featured_link}}
|
||||
{{topic-featured-link topic}}
|
||||
{{/if}}
|
||||
{{plugin-outlet "topic-list-after-title"}}
|
||||
{{#if showTopicPostBadges}}
|
||||
{{raw "topic-post-badges" unread=topic.unread newPosts=topic.displayNewPosts unseen=topic.unseen url=topic.lastUnreadUrl}}
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
{{#d-modal-body title="group.edit.title" class="edit-group groups"}}
|
||||
<form class="form-horizontal">
|
||||
<label for='title'>{{i18n 'group.title'}}</label>
|
||||
{{input type='text' name='title' value=model.title class='edit-group-title'}}
|
||||
|
||||
<label for='bio'>{{i18n 'group.bio'}}</label>
|
||||
{{d-editor value=model.bio_raw class="edit-group-bio"}}
|
||||
|
||||
{{group-flair-inputs model=model}}
|
||||
</form>
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{d-button action="save" class="btn-primary" disabled=saving label="save"}}
|
||||
<a {{action "closeModal"}}>{{i18n 'cancel'}}</a>
|
||||
</div>
|
||||
@ -86,6 +86,13 @@
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if model.featured_link_changes}}
|
||||
<div class='row'>
|
||||
{{model.featured_link_changes.previous}}
|
||||
→
|
||||
{{model.featured_link_changes.current}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{plugin-outlet "post-revisions"}}
|
||||
|
||||
|
||||
@ -25,6 +25,9 @@
|
||||
{{category-chooser valueAttribute="id" value=buffered.category_id}}
|
||||
{{/if}}
|
||||
|
||||
{{#if canEditTopicFeaturedLink}}
|
||||
{{text-field type="url" value=buffered.featured_link id='topic-featured-link' placeholderKey="composer.topic_featured_link_placeholder"}}
|
||||
{{/if}}
|
||||
{{#if canEditTags}}
|
||||
<br>
|
||||
{{tag-chooser tags=buffered.tags categoryId=buffered.category_id}}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<div class="pull-right">
|
||||
{{d-button icon="plus" action="showInvite" label="user.invited.create" class="btn"}}
|
||||
{{#if canBulkInvite}}
|
||||
{{resumable-upload target="/invites/upload" success="uploadSuccess" error="uploadError" uploadText=uploadText}}
|
||||
{{csv-uploader uploading=uploading}}
|
||||
{{/if}}
|
||||
{{#if showReinviteAllButton}}
|
||||
{{#if reinvitedAll}}
|
||||
|
||||
@ -23,12 +23,12 @@
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{plugin-outlet "user-activity-bottom"}}
|
||||
{{plugin-outlet "user-activity-bottom" tagName='li'}}
|
||||
{{/mobile-nav}}
|
||||
|
||||
{{#if viewingSelf}}
|
||||
<div class='user-archive'>
|
||||
{{d-button action="exportUserArchive" label="user.download_archive" icon="download"}}
|
||||
{{d-button action="exportUserArchive" label="user.download_archive.button_text" icon="download"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/d-section}}
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
{{#if model.noContent}}
|
||||
<div class='no-content'>
|
||||
{{{model.noContentHelp}}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#user-stream stream=model}}
|
||||
{{#each model.content as |item|}}
|
||||
{{stream-item item=item removeBookmark="removeBookmark"}}
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
<li>
|
||||
{{user-stat value=model.likes_received label="user.summary.likes_received"}}
|
||||
</li>
|
||||
{{plugin-outlet "user-summary-stat" tagName="li"}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@ -37,4 +37,4 @@ createWidget('avatar-flair', {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ import { iconNode } from 'discourse/helpers/fa-icon-node';
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import RawHtml from 'discourse/widgets/raw-html';
|
||||
import { tagNode } from 'discourse/lib/render-tag';
|
||||
import { topicFeaturedLinkNode } from 'discourse/lib/render-topic-featured-link';
|
||||
|
||||
export default createWidget('header-topic-info', {
|
||||
tagName: 'div.extra-info-wrapper',
|
||||
@ -44,12 +45,19 @@ export default createWidget('header-topic-info', {
|
||||
title.push(this.attach('category-link', { category }));
|
||||
}
|
||||
|
||||
const extra = [];
|
||||
if (this.siteSettings.tagging_enabled) {
|
||||
const tags = topic.get('tags') || [];
|
||||
if (tags.length) {
|
||||
title.push(h('div.list-tags', tags.map(tagNode)));
|
||||
extra.push(h('div.list-tags', tags.map(tagNode)));
|
||||
}
|
||||
}
|
||||
if (this.siteSettings.topic_featured_link_enabled) {
|
||||
extra.push(topicFeaturedLinkNode(attrs.topic));
|
||||
}
|
||||
if (extra) {
|
||||
title.push(h('div.topic-header-extra', extra));
|
||||
}
|
||||
}
|
||||
|
||||
const contents = h('div.title-wrapper', title);
|
||||
|
||||
@ -5,7 +5,6 @@ import { WidgetClickHook,
|
||||
WidgetDragHook } from 'discourse/widgets/hooks';
|
||||
import { h } from 'virtual-dom';
|
||||
import DecoratorHelper from 'discourse/widgets/decorator-helper';
|
||||
import { TARGET_NAME } from 'discourse/mixins/delegated-actions';
|
||||
|
||||
function emptyContent() { }
|
||||
|
||||
@ -272,7 +271,7 @@ export default class Widget {
|
||||
|
||||
if (target) {
|
||||
// TODO: Use ember closure actions
|
||||
const actions = target[TARGET_NAME] || target.actionHooks || {};
|
||||
const actions = target.actions || target.actionHooks || {};
|
||||
const method = actions[actionName];
|
||||
if (method) {
|
||||
promise = method.call(target, param);
|
||||
|
||||
@ -696,38 +696,6 @@ section.details {
|
||||
width: 100%;
|
||||
border-color: dark-light-choose(scale-color($primary, $lightness: 75%), scale-color($secondary, $lightness: 25%));
|
||||
}
|
||||
.avatar-flair-preview {
|
||||
position: relative;
|
||||
width: 45px;
|
||||
|
||||
.avatar-wrapper {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
}
|
||||
.form-horizontal {
|
||||
.flair_inputs {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.flair_left {
|
||||
float: left;
|
||||
width: 60%;
|
||||
input[name=flair_url] {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.flair_right {
|
||||
float: left;
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.row.groups {
|
||||
input[type='text'].flair_bg_color, input[type='text'].flair_color {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
// Customise area
|
||||
|
||||
@ -88,6 +88,10 @@ html.anon .topic-list a.title:visited:not(.badge-notification) {color: dark-ligh
|
||||
}
|
||||
}
|
||||
|
||||
.topic-featured-link {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.topic-excerpt {
|
||||
font-size: 0.929em;
|
||||
margin-top: 8px;
|
||||
|
||||
@ -187,6 +187,10 @@ div.ac-wrap {
|
||||
}
|
||||
}
|
||||
|
||||
#reply-control.topic-featured-link-only.open {
|
||||
.wmd-controls { display: none; }
|
||||
}
|
||||
|
||||
#cancel-file-upload {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
121
app/assets/stylesheets/common/base/group.scss
Normal file
121
app/assets/stylesheets/common/base/group.scss
Normal file
@ -0,0 +1,121 @@
|
||||
.group-header {
|
||||
font-size: 2.142em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
font-weight: normal;
|
||||
margin-top: 5px;
|
||||
color: dark-light-diff($primary, $secondary, 50%, -50%);
|
||||
}
|
||||
|
||||
.group-details-container {
|
||||
background: rgba(230, 230, 230, 0.3);
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
table.group-members {
|
||||
width: 100%;
|
||||
|
||||
th, tr {
|
||||
border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
tr {
|
||||
.user-info {
|
||||
display: block;
|
||||
}
|
||||
|
||||
td {
|
||||
color: dark-light-diff($primary, $secondary, 50%, -50%);
|
||||
padding: 0.8em 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group-owner-label {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.group-details {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.group-details {
|
||||
span {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.avatar-flair {
|
||||
$size: 50px;
|
||||
|
||||
background-size: $size;
|
||||
height: $size;
|
||||
width: $size;
|
||||
|
||||
i {
|
||||
font-size: $size !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group-edit {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.groups.edit-group .form-horizontal {
|
||||
textarea {
|
||||
width: 99%;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 80% !important;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.group-flair-inputs {
|
||||
display: inline-block;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.group-flair-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.group-flair-right {
|
||||
float: left;
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-flair-preview {
|
||||
position: relative;
|
||||
width: 45px;
|
||||
|
||||
.avatar-wrapper {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#add-user-to-group {
|
||||
margin: 0px;
|
||||
|
||||
.ac-wrap {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.add {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
@ -30,6 +30,7 @@ $input-width: 220px;
|
||||
.disclaimer {
|
||||
font-size: 0.9em;
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.user-field.confirm {
|
||||
|
||||
@ -27,18 +27,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.extra-info-wrapper {
|
||||
.list-tags {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.discourse-tag {
|
||||
-webkit-animation: fadein .7s;
|
||||
animation: fadein .7s;
|
||||
}
|
||||
.topic-header-extra .discourse-tag {
|
||||
-webkit-animation: fadein .7s;
|
||||
animation: fadein .7s;
|
||||
}
|
||||
|
||||
|
||||
.add-tags .select2 {
|
||||
margin: 0;
|
||||
}
|
||||
@ -136,11 +129,11 @@ $tag-color: scale-color($primary, $lightness: 40%);
|
||||
top: -0.1em;
|
||||
}
|
||||
|
||||
header .discourse-tag {color: $tag-color !important; }
|
||||
header .discourse-tag {color: $tag-color }
|
||||
|
||||
.list-tags {
|
||||
margin-right: 3px;
|
||||
display: inline;
|
||||
margin: 0 0 0 5px;
|
||||
font-size: 0.857em;
|
||||
}
|
||||
|
||||
@ -171,24 +164,6 @@ header .discourse-tag {color: $tag-color !important; }
|
||||
left: auto;
|
||||
}
|
||||
|
||||
.bullet + .list-tags {
|
||||
display: block;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.bar + .list-tags {
|
||||
line-height: 1.25;
|
||||
.discourse-tag {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.box + .list-tags {
|
||||
display: inline-block;
|
||||
margin: 5px 0 0 5px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.tag-sort-options {
|
||||
margin-bottom: 20px;
|
||||
a {
|
||||
|
||||
@ -9,6 +9,10 @@
|
||||
.badge-wrapper {
|
||||
float: left;
|
||||
}
|
||||
|
||||
a.topic-featured-link {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
a.badge-category {
|
||||
@ -47,7 +51,7 @@
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#suggested-topics h3 .badge-wrapper.bullet span.badge-category, {
|
||||
#suggested-topics h3 .badge-wrapper.bullet span.badge-category {
|
||||
// Override vertical-align: text-top from `badges.css.scss`
|
||||
vertical-align: baseline;
|
||||
line-height: 1.2;
|
||||
@ -133,3 +137,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.topic-featured-link {
|
||||
display: inline-block;
|
||||
text-transform: lowercase;
|
||||
color: #858585;
|
||||
font-size: 0.875rem;
|
||||
|
||||
&::before {
|
||||
position: relative;
|
||||
top: 0.1em;
|
||||
padding-right: 3px;
|
||||
font-family: FontAwesome;
|
||||
content: "\f08e";
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,6 +133,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.extra-info-wrapper .title-wrapper .badge-wrapper.bar {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.autocomplete, td.category {
|
||||
.badge-wrapper {
|
||||
max-width: 230px;
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
@import "desktop/history";
|
||||
@import "desktop/queued-posts";
|
||||
@import "desktop/menu-panel";
|
||||
@import "desktop/group";
|
||||
|
||||
/* These files doesn't actually exist, they are injected by DiscourseSassImporter. */
|
||||
|
||||
|
||||
@ -151,6 +151,10 @@
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.topic-featured-link {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.topic-list {
|
||||
.posts {
|
||||
width: 100%;
|
||||
|
||||
@ -298,6 +298,11 @@
|
||||
background-color: dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
}
|
||||
}
|
||||
#topic-featured-link {
|
||||
padding: 7px 10px;
|
||||
margin: 6px 10px 3px 0;
|
||||
width: 400px;
|
||||
}
|
||||
.d-editor-input:disabled {
|
||||
background-color: dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
}
|
||||
@ -465,6 +470,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
#reply-control.topic-featured-link-only.open {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.control-row.reply-area {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
|
||||
12
app/assets/stylesheets/desktop/group.scss
Normal file
12
app/assets/stylesheets/desktop/group.scss
Normal file
@ -0,0 +1,12 @@
|
||||
.group-outlet {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.group-nav {
|
||||
width: 20%;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.group-details {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@ -505,13 +505,13 @@ video {
|
||||
.extra-info-wrapper {
|
||||
overflow: hidden;
|
||||
|
||||
.badge-wrapper, i, .topic-link {
|
||||
.badge-wrapper, i, .topic-link {
|
||||
-webkit-animation: fadein .7s;
|
||||
animation: fadein .7s;
|
||||
}
|
||||
|
||||
.topic-statuses {
|
||||
i { color: $header_primary; }
|
||||
i { color: $header_primary; }
|
||||
i.fa-envelope { color: $danger; }
|
||||
.unpinned { color: $header_primary; }
|
||||
}
|
||||
@ -523,6 +523,26 @@ video {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.topic-header-extra {
|
||||
margin: 0 0 0 5px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.bullet + .topic-header-extra {
|
||||
display: block;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.bar + .topic-header-extra {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.box + .topic-header-extra {
|
||||
display: inline-block;
|
||||
margin: 0 0 0 5px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
/* default docked header CSS for all topics, including those without categories */
|
||||
|
||||
@ -133,50 +133,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
table.group-members {
|
||||
width: 100%;
|
||||
p {
|
||||
max-width: 600px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
th {
|
||||
padding: 0.5em;
|
||||
text-align: right;
|
||||
border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
}
|
||||
td.avatar {
|
||||
width: 60px;
|
||||
position: relative;
|
||||
.is-owner {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 20px;
|
||||
color: dark-light-diff($primary, $secondary, 50%, -50%);
|
||||
}
|
||||
}
|
||||
td.remove-user {
|
||||
text-align: right;
|
||||
}
|
||||
td {
|
||||
padding: 0.5em;
|
||||
border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
img {
|
||||
margin-right: 10px;
|
||||
}
|
||||
span.text {
|
||||
float: right;
|
||||
font-size: 1.2em;
|
||||
color: dark-light-diff($primary, $secondary, 50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-right.groups {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.user-right {
|
||||
width: 900px;
|
||||
margin-top: 20px;
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
@import "mobile/search";
|
||||
@import "mobile/emoji";
|
||||
@import "mobile/ring";
|
||||
@import "mobile/group";
|
||||
|
||||
/* These files doesn't actually exist, they are injected by DiscourseSassImporter. */
|
||||
|
||||
|
||||
@ -16,7 +16,10 @@ display: none !important; // can be removed if inline JS CSS is removed from com
|
||||
input {
|
||||
background: $secondary;
|
||||
color: $primary;
|
||||
border-color: blend-primary-secondary(15%);
|
||||
padding: 4px;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0, .3);
|
||||
border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
}
|
||||
|
||||
#reply-control {
|
||||
|
||||
49
app/assets/stylesheets/mobile/group.scss
Normal file
49
app/assets/stylesheets/mobile/group.scss
Normal file
@ -0,0 +1,49 @@
|
||||
.group {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.group-header {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
margin: 5px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.group-nav, .group-outlet {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.group-details-container {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.group-nav.mobile-nav {
|
||||
margin-bottom: 15px;
|
||||
|
||||
> li {
|
||||
a {
|
||||
color: white;
|
||||
|
||||
.fa { color: white; }
|
||||
}
|
||||
}
|
||||
|
||||
background-color: $quaternary;
|
||||
}
|
||||
|
||||
table.group-members {
|
||||
th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
tr {
|
||||
.user-info {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,7 +19,11 @@
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 35%), scale-color($secondary, $lightness: 65%));
|
||||
}
|
||||
label { float: left; display: block; }
|
||||
textarea, input, select {font-size: 1.143em; clear: left; margin-top: 0; }
|
||||
textarea, input, select {
|
||||
font-size: 1.143em;
|
||||
clear: left;
|
||||
margin-top: 0;
|
||||
}
|
||||
td { padding: 4px; }
|
||||
}
|
||||
|
||||
|
||||
@ -38,15 +38,16 @@
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
// an ember metamorph is inserted between btn's sometimes, breaking this rule, but only on mobile for some reason:
|
||||
// .modal-footer .btn + .btn {
|
||||
.modal-footer .btn {
|
||||
// we need a little extra room on mobile for the
|
||||
// stuff inside the footer to fit
|
||||
.modal-footer {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.modal-footer .btn + .btn {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.modal-footer .btn-group .btn + .btn {
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
// we need tighter spacing on mobile for header
|
||||
|
||||
@ -36,7 +36,7 @@ class Admin::GroupsController < Admin::AdminController
|
||||
def create
|
||||
group = Group.new
|
||||
|
||||
group.name = (params[:name] || '').strip
|
||||
group.name = (group_params[:name] || '').strip
|
||||
save_group(group)
|
||||
end
|
||||
|
||||
@ -44,29 +44,29 @@ class Admin::GroupsController < Admin::AdminController
|
||||
group = Group.find(params[:id])
|
||||
|
||||
# group rename is ignored for automatic groups
|
||||
group.name = params[:name] if params[:name] && !group.automatic
|
||||
group.name = group_params[:name] if group_params[:name] && !group.automatic
|
||||
save_group(group)
|
||||
end
|
||||
|
||||
def save_group(group)
|
||||
group.alias_level = params[:alias_level].to_i if params[:alias_level].present?
|
||||
group.visible = params[:visible] == "true"
|
||||
grant_trust_level = params[:grant_trust_level].to_i
|
||||
group.alias_level = group_params[:alias_level].to_i if group_params[:alias_level].present?
|
||||
group.visible = group_params[:visible] == "true"
|
||||
grant_trust_level = group_params[:grant_trust_level].to_i
|
||||
group.grant_trust_level = (grant_trust_level > 0 && grant_trust_level <= 4) ? grant_trust_level : nil
|
||||
|
||||
group.automatic_membership_email_domains = params[:automatic_membership_email_domains] unless group.automatic
|
||||
group.automatic_membership_retroactive = params[:automatic_membership_retroactive] == "true" unless group.automatic
|
||||
group.automatic_membership_email_domains = group_params[:automatic_membership_email_domains] unless group.automatic
|
||||
group.automatic_membership_retroactive = group_params[:automatic_membership_retroactive] == "true" unless group.automatic
|
||||
|
||||
group.primary_group = group.automatic ? false : params["primary_group"] == "true"
|
||||
group.primary_group = group.automatic ? false : group_params["primary_group"] == "true"
|
||||
|
||||
group.incoming_email = group.automatic ? nil : params[:incoming_email]
|
||||
group.incoming_email = group.automatic ? nil : group_params[:incoming_email]
|
||||
|
||||
title = params[:title] if params[:title].present?
|
||||
title = group_params[:title] if group_params[:title].present?
|
||||
group.title = group.automatic ? nil : title
|
||||
|
||||
group.flair_url = params[:flair_url].presence
|
||||
group.flair_bg_color = params[:flair_bg_color].presence
|
||||
group.flair_color = params[:flair_color].presence
|
||||
group.flair_url = group_params[:flair_url].presence
|
||||
group.flair_bg_color = group_params[:flair_bg_color].presence
|
||||
group.flair_color = group_params[:flair_color].presence
|
||||
|
||||
if group.save
|
||||
Group.reset_counters(group.id, :group_users)
|
||||
@ -124,7 +124,18 @@ class Admin::GroupsController < Admin::AdminController
|
||||
|
||||
protected
|
||||
|
||||
def can_not_modify_automatic
|
||||
render json: {errors: I18n.t('groups.errors.can_not_modify_automatic')}, status: 422
|
||||
end
|
||||
def can_not_modify_automatic
|
||||
render json: {errors: I18n.t('groups.errors.can_not_modify_automatic')}, status: 422
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def group_params
|
||||
params.require(:group).permit(
|
||||
:name, :alias_level, :visible, :automatic_membership_email_domains,
|
||||
:automatic_membership_retroactive, :title, :primary_group,
|
||||
:grant_trust_level, :incoming_email, :flair_url, :flair_bg_color,
|
||||
:flair_color
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -110,6 +110,32 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
def self.last_ar_cache_reset
|
||||
@last_ar_cache_reset
|
||||
end
|
||||
|
||||
def self.last_ar_cache_reset=(val)
|
||||
@last_ar_cache_reset
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::StatementInvalid do |e|
|
||||
|
||||
last_cache_reset = ApplicationController.last_ar_cache_reset
|
||||
|
||||
if e.message =~ /UndefinedColumn/ && (last_cache_reset.nil? || last_cache_reset < 30.seconds.ago)
|
||||
Rails.logger.warn "Clear Active Record cache cause schema appears to have changed!"
|
||||
|
||||
ApplicationController.last_ar_cache_reset = Time.zone.now
|
||||
|
||||
ActiveRecord::Base.connection.query_cache.clear
|
||||
(ActiveRecord::Base.connection.tables - %w[schema_migrations]).each do |table|
|
||||
table.classify.constantize.reset_column_information rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
raise e
|
||||
end
|
||||
|
||||
class PluginDisabled < StandardError; end
|
||||
|
||||
# Handles requests for giant IDs that throw pg exceptions
|
||||
@ -130,7 +156,7 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
rescue_from Discourse::ReadOnly do
|
||||
render_json_error I18n.t('read_only_mode_enabled'), type: :read_only, status: 405
|
||||
render_json_error I18n.t('read_only_mode_enabled'), type: :read_only, status: 503
|
||||
end
|
||||
|
||||
def rescue_discourse_actions(type, status_code, include_ember=false)
|
||||
@ -382,7 +408,7 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
def preload_current_user_data
|
||||
store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, scope: guardian, root: false)))
|
||||
report = TopicTrackingState.report(current_user.id)
|
||||
report = TopicTrackingState.report(current_user)
|
||||
serializer = ActiveModel::ArraySerializer.new(report, each_serializer: TopicTrackingStateSerializer)
|
||||
store_preloaded("topicTrackingStates", MultiJson.dump(serializer))
|
||||
end
|
||||
@ -465,7 +491,7 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def mini_profiler_enabled?
|
||||
defined?(Rack::MiniProfiler) && guardian.is_developer?
|
||||
defined?(Rack::MiniProfiler) && (guardian.is_developer? || Rails.env.development?)
|
||||
end
|
||||
|
||||
def authorize_mini_profiler
|
||||
|
||||
@ -59,6 +59,7 @@ class FinishInstallationController < ApplicationController
|
||||
end
|
||||
|
||||
def ensure_no_admins
|
||||
preload_anonymous_data
|
||||
raise Discourse::InvalidAccess.new unless SiteSetting.has_login_hint?
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,27 +1,26 @@
|
||||
class GroupsController < ApplicationController
|
||||
|
||||
before_filter :ensure_logged_in, only: [:set_notifications, :mentionable]
|
||||
before_filter :ensure_logged_in, only: [
|
||||
:set_notifications,
|
||||
:mentionable,
|
||||
:update
|
||||
]
|
||||
|
||||
skip_before_filter :preload_json, :check_xhr, only: [:posts_feed, :mentions_feed]
|
||||
|
||||
def show
|
||||
render_serialized(find_group(:id), GroupShowSerializer, root: 'basic_group')
|
||||
end
|
||||
|
||||
def counts
|
||||
group = find_group(:group_id)
|
||||
def update
|
||||
group = Group.find(params[:id])
|
||||
guardian.ensure_can_edit!(group)
|
||||
|
||||
counts = {
|
||||
posts: group.posts_for(guardian).count,
|
||||
topics: group.posts_for(guardian).where(post_number: 1).count,
|
||||
mentions: group.mentioned_posts_for(guardian).count,
|
||||
members: group.users.count,
|
||||
}
|
||||
|
||||
if guardian.can_see_group_messages?(group)
|
||||
counts[:messages] = group.messages_for(guardian).where(post_number: 1).count
|
||||
if group.update_attributes(group_params)
|
||||
render json: success_json
|
||||
else
|
||||
render_json_error(group)
|
||||
end
|
||||
|
||||
render json: { counts: counts }
|
||||
end
|
||||
|
||||
def posts
|
||||
@ -169,11 +168,21 @@ class GroupsController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def find_group(param_name)
|
||||
name = params.require(param_name)
|
||||
group = Group.find_by("lower(name) = ?", name.downcase)
|
||||
guardian.ensure_can_see!(group)
|
||||
group
|
||||
end
|
||||
def group_params
|
||||
params.require(:group).permit(
|
||||
:flair_url,
|
||||
:flair_bg_color,
|
||||
:flair_color,
|
||||
:bio_raw,
|
||||
:title
|
||||
)
|
||||
end
|
||||
|
||||
def find_group(param_name)
|
||||
name = params.require(param_name)
|
||||
group = Group.find_by("lower(name) = ?", name.downcase)
|
||||
guardian.ensure_can_see!(group)
|
||||
group
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -6,7 +6,7 @@ class InvitesController < ApplicationController
|
||||
skip_before_filter :check_xhr, :preload_json
|
||||
skip_before_filter :redirect_to_login_if_required
|
||||
|
||||
before_filter :ensure_logged_in, only: [:destroy, :create, :create_invite_link, :resend_invite, :resend_all_invites, :check_csv_chunk, :upload_csv_chunk]
|
||||
before_filter :ensure_logged_in, only: [:destroy, :create, :create_invite_link, :resend_invite, :resend_all_invites, :upload_csv]
|
||||
before_filter :ensure_new_registrations_allowed, only: [:show, :redeem_disposable_invite]
|
||||
before_filter :ensure_not_logged_in, only: [:show, :redeem_disposable_invite]
|
||||
|
||||
@ -147,48 +147,29 @@ class InvitesController < ApplicationController
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
def check_csv_chunk
|
||||
def upload_csv
|
||||
guardian.ensure_can_bulk_invite_to_forum!(current_user)
|
||||
|
||||
filename = params.fetch(:resumableFilename)
|
||||
identifier = params.fetch(:resumableIdentifier)
|
||||
chunk_number = params.fetch(:resumableChunkNumber)
|
||||
current_chunk_size = params.fetch(:resumableCurrentChunkSize).to_i
|
||||
file = params[:file] || params[:files].first
|
||||
name = params[:name] || File.basename(file.original_filename, ".*")
|
||||
extension = File.extname(file.original_filename)
|
||||
|
||||
# path to chunk file
|
||||
chunk = Invite.chunk_path(identifier, filename, chunk_number)
|
||||
# check chunk upload status
|
||||
status = HandleChunkUpload.check_chunk(chunk, current_chunk_size: current_chunk_size)
|
||||
|
||||
render nothing: true, status: status
|
||||
end
|
||||
|
||||
def upload_csv_chunk
|
||||
guardian.ensure_can_bulk_invite_to_forum!(current_user)
|
||||
|
||||
filename = params.fetch(:resumableFilename)
|
||||
return render status: 415, text: I18n.t("bulk_invite.file_should_be_csv") unless (filename.to_s.end_with?(".csv") || filename.to_s.end_with?(".txt"))
|
||||
|
||||
file = params.fetch(:file)
|
||||
identifier = params.fetch(:resumableIdentifier)
|
||||
chunk_number = params.fetch(:resumableChunkNumber).to_i
|
||||
chunk_size = params.fetch(:resumableChunkSize).to_i
|
||||
total_size = params.fetch(:resumableTotalSize).to_i
|
||||
current_chunk_size = params.fetch(:resumableCurrentChunkSize).to_i
|
||||
|
||||
# path to chunk file
|
||||
chunk = Invite.chunk_path(identifier, filename, chunk_number)
|
||||
# upload chunk
|
||||
HandleChunkUpload.upload_chunk(chunk, file: file)
|
||||
|
||||
uploaded_file_size = chunk_number * chunk_size
|
||||
# when all chunks are uploaded
|
||||
if uploaded_file_size + current_chunk_size >= total_size
|
||||
# handle bulk_invite processing in a background thread
|
||||
Jobs.enqueue(:bulk_invite, filename: filename, identifier: identifier, chunks: chunk_number, current_user_id: current_user.id)
|
||||
Scheduler::Defer.later("Upload CSV") do
|
||||
begin
|
||||
data = if extension == ".csv"
|
||||
path = Invite.create_csv(file, name)
|
||||
Jobs.enqueue(:bulk_invite, filename: "#{name}.csv", current_user_id: current_user.id)
|
||||
{url: path}
|
||||
else
|
||||
failed_json.merge(errors: [I18n.t("bulk_invite.file_should_be_csv")])
|
||||
end
|
||||
rescue
|
||||
failed_json.merge(errors: [I18n.t("bulk_invite.error")])
|
||||
end
|
||||
MessageBus.publish("/uploads/csv", data.as_json, user_ids: [current_user.id])
|
||||
end
|
||||
|
||||
render nothing: true
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
def fetch_username
|
||||
|
||||
@ -574,7 +574,6 @@ class PostsController < ApplicationController
|
||||
|
||||
end
|
||||
|
||||
params.require(:raw)
|
||||
result = params.permit(*permitted).tap do |whitelisted|
|
||||
whitelisted[:image_sizes] = params[:image_sizes]
|
||||
# TODO this does not feel right, we should name what meta_data is allowed
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user