Version bump

This commit is contained in:
Neil Lalonde 2016-12-28 18:15:26 -05:00
commit 5a31a7b3d3
333 changed files with 5332 additions and 3779 deletions

View File

@ -52,7 +52,7 @@ before_script:
- bundle exec rake db:create db:migrate
install:
- bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails rails-observers seed-fu; fi"
- bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails seed-fu; fi"
- bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi"
script: "bundle exec rspec && bundle exec rake plugin:spec && bundle exec rake qunit:test['200000']"

View File

@ -9,7 +9,6 @@ end
if rails_master?
gem 'arel', git: 'https://github.com/rails/arel.git'
gem 'rails', git: 'https://github.com/rails/rails.git'
gem 'rails-observers', git: 'https://github.com/rails/rails-observers.git'
gem 'seed-fu', git: 'https://github.com/SamSaffron/seed-fu.git', branch: 'discourse'
else
# Rails 5 is going to ship with Action Cable, we have no use for it as
@ -29,8 +28,6 @@ else
# gem 'railties'
# gem 'sprockets-rails'
gem 'rails', '~> 4.2'
gem 'rails-observers'
gem 'seed-fu', '~> 2.3.5'
end
@ -48,7 +45,8 @@ gem 'onebox'
gem 'http_accept_language', '~>2.0.5', require: false
gem 'ember-rails', '0.18.5'
gem 'ember-source', '2.4.6'
gem 'ember-source', '2.10.0'
gem 'ember-handlebars-template', '0.7.5'
gem 'barber'
gem 'babel-transpiler'
@ -173,14 +171,13 @@ gem 'rack-mini-profiler', require: false
gem 'unicorn', require: false
gem 'puma', require: false
gem 'rbtrace', require: false, platform: :mri
gem 'gc_tracer', require: false, platform: :mri
# required for feed importing and embedding
#
gem 'ruby-readability', require: false
gem 'simple-rss', require: false
gem 'gctools', require: false, platform: :mri_21
begin
gem 'stackprof', require: false, platform: [:mri_21, :mri_22, :mri_23]

View File

@ -52,7 +52,7 @@ GEM
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
execjs (~> 2.0)
barber (0.11.1)
barber (0.11.2)
ember-source (>= 1.0, < 3)
execjs (>= 1.2, < 3)
better_errors (2.1.1)
@ -82,7 +82,7 @@ GEM
email_reply_trimmer (0.1.6)
ember-data-source (2.2.1)
ember-source (>= 1.8, < 3.0)
ember-handlebars-template (0.7.4)
ember-handlebars-template (0.7.5)
barber (>= 0.11.0)
sprockets (>= 3.3, < 4)
ember-rails (0.18.5)
@ -92,7 +92,7 @@ GEM
ember-source (>= 1.1.0)
jquery-rails (>= 1.0.17)
railties (>= 3.1)
ember-source (2.4.6)
ember-source (2.10.0)
erubis (2.7.0)
eventmachine (1.2.0.1)
excon (0.53.0)
@ -113,7 +113,7 @@ GEM
foreman (0.82.0)
thor (~> 0.19.1)
fspath (2.1.1)
gctools (0.2.3)
gc_tracer (1.5.1)
globalid (0.3.7)
activesupport (>= 4.1.0)
guess_html_encoding (0.0.11)
@ -149,7 +149,7 @@ GEM
lru_redux (1.1.0)
mail (2.6.4)
mime-types (>= 1.16, < 4)
memory_profiler (0.9.6)
memory_profiler (0.9.7)
message_bus (2.0.2)
rack (>= 1.1.3)
metaclass (0.0.4)
@ -209,7 +209,7 @@ GEM
omniauth-twitter (1.2.1)
json (~> 1.3)
omniauth-oauth (~> 1.1)
onebox (1.6.7)
onebox (1.6.8)
fast_blank (>= 1.0.0)
htmlentities (~> 4.3.4)
moneta (~> 0.8)
@ -231,7 +231,7 @@ GEM
pry (>= 0.9.10)
puma (3.6.0)
r2 (0.2.6)
rack (1.6.4)
rack (1.6.5)
rack-mini-profiler (0.10.1)
rack (>= 1.2.0)
rack-openid (1.3.1)
@ -260,8 +260,6 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
rails_multisite (1.0.6)
rails (> 4.2, < 5)
railties (4.2.7.1)
@ -401,8 +399,9 @@ DEPENDENCIES
discourse-qunit-rails
discourse_fastimage (= 2.0.3)
email_reply_trimmer (= 0.1.6)
ember-handlebars-template (= 0.7.5)
ember-rails (= 0.18.5)
ember-source (= 2.4.6)
ember-source (= 2.10.0)
excon
execjs
fabrication (= 2.9.8)
@ -413,7 +412,7 @@ DEPENDENCIES
fast_xs
flamegraph
foreman
gctools
gc_tracer
highline
hiredis
htmlentities
@ -452,7 +451,6 @@ DEPENDENCIES
rack-mini-profiler
rack-protection
rails (~> 4.2)
rails-observers
rails_multisite
rake
rb-fsevent

View File

@ -36,6 +36,7 @@ export default Ember.Component.extend({
loadScript("/javascripts/ace/ace.js", { scriptTag: true }).then(() => {
window.ace.require(['ace/ace'], loadedAce => {
if (!this.element || this.isDestroying || this.isDestroyed) { return; }
const editor = loadedAce.edit(this.$('.ace')[0]);
editor.setTheme("ace/theme/chrome");

View File

@ -68,7 +68,7 @@ export default Ember.Controller.extend(BufferedContent, {
this.get('model').save(data).then(() => {
if (newBadge) {
const adminBadges = this.get('adminBadges.model');
if (!adminBadges.contains(model)) {
if (!adminBadges.includes(model)) {
adminBadges.pushObject(model);
}
this.transitionToRoute('adminBadges.show', model.get('id'));

View File

@ -31,6 +31,29 @@ export default Ember.Controller.extend(CanCheckEmails, {
}.property('model.user_fields.[]'),
actions: {
impersonate() { return this.get("model").impersonate(); },
logOut() { return this.get("model").logOut(); },
resetBounceScore() { return this.get("model").resetBounceScore(); },
refreshBrowsers() { return this.get("model").refreshBrowsers(); },
approve() { return this.get("model").approve(); },
deactivate() { return this.get("model").deactivate(); },
sendActivationEmail() { return this.get("model").sendActivationEmail(); },
activate() { return this.get("model").activate(); },
revokeAdmin() { return this.get("model").revokeAdmin(); },
grantAdmin() { return this.get("model").grantAdmin(); },
revokeModeration() { return this.get("model").revokeModeration(); },
grantModeration() { return this.get("model").grantModeration(); },
saveTrustLevel() { return this.get("model").saveTrustLevel(); },
restoreTrustLevel() { return this.get("model").restoreTrustLevel(); },
lockTrustLevel(locked) { return this.get("model").lockTrustLevel(locked); },
unsuspend() { return this.get("model").unsuspend(); },
unblock() { return this.get("model").unblock(); },
block() { return this.get("model").block(); },
deleteAllPosts() { return this.get("model").deleteAllPosts(); },
anonymize() { return this.get('model').anonymize(); },
destroy() { return this.get('model').destroy(); },
toggleTitleEdit() {
this.set('userTitleValue', this.get('model.title'));
this.toggleProperty('editingTitle');
@ -107,14 +130,6 @@ export default Ember.Controller.extend(CanCheckEmails, {
if (result) { self.get('model').revokeApiKey(); }
}
);
},
anonymize() {
this.get('model').anonymize();
},
destroy() {
this.get('model').destroy();
}
}

View File

@ -257,7 +257,7 @@ const AdminUser = Discourse.User.extend({
});
},
log_out() {
logOut() {
return ajax("/admin/users/" + this.id + "/log_out", {
type: 'POST',
data: { username_or_email: this.get('username') }

View File

@ -23,7 +23,7 @@
{{nav-item route='admin.backups' label='admin.backups.title'}}
{{/if}}
{{nav-item route='adminPlugins' label='admin.plugins.title'}}
{{plugin-outlet "admin-menu" tagName="li"}}
{{plugin-outlet name="admin-menu" connectorTagName="li"}}
</ul>
<div class='boxed white admin-content'>

View File

@ -1,4 +1,4 @@
{{plugin-outlet "admin-dashboard-top"}}
{{plugin-outlet name="admin-dashboard-top"}}
{{#conditional-loading-spinner condition=loading}}
<div class="dashboard-left">

View File

@ -108,7 +108,7 @@
{{#if siteSettings.email_in}}
<label for="incoming_email">{{i18n 'admin.groups.incoming_email'}}</label>
{{text-field name="incoming_email" value=model.incoming_email placeholderKey="admin.groups.incoming_email_placeholder"}}
{{plugin-outlet "group-email-in"}}
{{plugin-outlet name="group-email-in" args=(hash model=model)}}
{{/if}}
{{/unless}}

View File

@ -9,16 +9,10 @@
{{/if}}
{{#if model.active}}
{{#if model.can_impersonate}}
<button class='btn btn-danger' {{action "impersonate" target="content"}} title="{{i18n 'admin.impersonate.help'}}">
{{fa-icon "crosshairs"}}
{{i18n 'admin.impersonate.title'}}
</button>
{{d-button class="btn-danger" action="impersonate" icon="crosshairs" label="admin.impersonate.title" title="admin.impersonate.help"}}
{{/if}}
{{#if currentUser.admin}}
<button class='btn' {{action "log_out" target="content"}}>
{{fa-icon "power-off"}}
{{i18n 'admin.user.log_out'}}
</button>
{{d-button action="logOut" icon="power-off" label="admin.user.log_out"}}
{{/if}}
{{/if}}
</div>
@ -49,7 +43,7 @@
{{#if model.email}}
<a href="mailto:{{unbound model.email}}">{{model.email}}</a>
{{else}}
<button class="btn" title="{{i18n 'admin.users.check_email.title'}}" {{action "checkEmail" this}}>{{fa-icon "envelope-o"}} {{i18n 'admin.users.check_email.text'}}</button>
{{d-button action="checkEmail" actionParam=model icon="envelope-o" label="admin.users.check_email.text" title="admin.users.check_email.title"}}
{{/if}}
</div>
</div>
@ -59,9 +53,7 @@
<div class='value'>{{model.bounceScore}}</div>
<div class='controls'>
{{#if model.canResetBounceScore}}
<button class='btn' {{action "resetBounceScore" target="content"}} title={{i18n "admin.user.reset_bounce_score.title"}}>
{{i18n "admin.user.reset_bounce_score.label"}}
</button>
{{d-button action="resetBounceScore" label="admin.user.reset_bounce_score.label" title="admin.user.reset_bounce_score.title"}}
{{/if}}
{{model.bounceScoreExplanation}}
</div>
@ -73,7 +65,7 @@
{{#if model.associated_accounts}}
{{model.associated_accounts}}
{{else}}
<button class="btn" title="{{i18n 'admin.users.check_email.title'}}" {{action "checkEmail" this}}>{{fa-icon "envelope-o"}} {{i18n 'admin.users.check_email.text'}}</button>
{{d-button action="checkEmail" actionParam=model icon="envelope-o" label="admin.users.check_email.text" title="admin.users.check_email.title"}}
{{/if}}
</div>
</div>
@ -108,9 +100,7 @@
<div class='value'>{{model.ip_address}}</div>
<div class='controls'>
{{#if currentUser.staff}}
<button class='btn' {{action "refreshBrowsers" target="content"}}>
{{i18n 'admin.user.refresh_browsers'}}
</button>
{{d-button action="refreshBrowsers" label="admin.user.refresh_browsers"}}
{{ip-lookup ip=model.ip_address userId=model.id}}
{{/if}}
</div>
@ -176,10 +166,7 @@
{{i18n 'admin.user.approve_success'}}
{{else}}
{{#if model.can_approve}}
<button class='btn' {{action "approve" target="content"}}>
{{fa-icon "check"}}
{{i18n 'admin.user.approve'}}
</button>
{{d-button action="approve" icon="check" label="admin.user.approve"}}
{{/if}}
{{/if}}
</div>
@ -198,21 +185,15 @@
<div class='controls'>
{{#if model.active}}
{{#if model.can_deactivate}}
<button class='btn' {{action "deactivate" target="content"}}>{{i18n 'admin.user.deactivate_account'}}</button>
{{d-button action="deactivate" label="admin.user.deactivate_account"}}
{{i18n 'admin.user.deactivate_explanation'}}
{{/if}}
{{else}}
{{#if model.can_send_activation_email}}
<button class='btn' {{action "sendActivationEmail" target="content"}}>
{{fa-icon "envelope"}}
{{i18n 'admin.user.send_activation_email'}}
</button>
{{d-button action="sendActivationEmail" icon="envelope" label="admin.user.send_activation_email"}}
{{/if}}
{{#if model.can_activate}}
<button class='btn' {{action "activate" target="content"}}>
{{fa-icon "check"}}
{{i18n 'admin.user.activate'}}
</button>
{{d-button action="activate" icon="check" label="admin.user.activate"}}
{{/if}}
{{/if}}
</div>
@ -243,16 +224,10 @@
<div class='value'>{{model.admin}}</div>
<div class='controls'>
{{#if model.can_revoke_admin}}
<button class='btn' {{action "revokeAdmin" target="content"}}>
{{fa-icon "shield"}}
{{i18n 'admin.user.revoke_admin'}}
</button>
{{d-button action="revokeAdmin" icon="shield" label="admin.user.revoke_admin"}}
{{/if}}
{{#if model.can_grant_admin}}
<button class='btn' {{action "grantAdmin" target="content"}}>
{{fa-icon "shield"}}
{{i18n 'admin.user.grant_admin'}}
</button>
{{d-button action="grantAdmin" icon="shield" label="admin.user.grant_admin"}}
{{/if}}
</div>
</div>
@ -262,16 +237,10 @@
<div class='value'>{{model.moderator}}</div>
<div class='controls'>
{{#if model.can_revoke_moderation}}
<button class='btn' {{action "revokeModeration" target="content"}}>
{{fa-icon "shield"}}
{{i18n 'admin.user.revoke_moderation'}}
</button>
{{d-button action="revokeModeration" icon="shield" label="admin.user.revoke_moderation"}}
{{/if}}
{{#if model.can_grant_moderation}}
<button class='btn' {{action "grantModeration" target="content"}}>
{{fa-icon "shield"}}
{{i18n 'admin.user.grant_moderation'}}
</button>
{{d-button action="grantModeration" icon="shield" label="admin.user.grant_moderation"}}
{{/if}}
</div>
</div>
@ -282,17 +251,17 @@
{{combo-box content=site.trustLevels value=model.trust_level nameProperty="detailedName"}}
{{#if model.dirty}}
<div>
<button class='btn ok no-text' {{action "saveTrustLevel" target="content"}}>{{fa-icon "check"}}</button>
<button class='btn cancel no-text' {{action "restoreTrustLevel" target="content"}}>{{fa-icon "times"}}</button>
{{d-button class="ok no-text" action="saveTrustLevel" icon="check"}}
{{d-button class="cancel no-text" action="restoreTrustLevel" icon="times"}}
</div>
{{/if}}
</div>
<div class="controls">
{{#if model.canLockTrustLevel}}
{{#if model.trust_level_locked}}
<i title='{{i18n 'admin.user.trust_level_locked_tip'}}' {{action "lockTrustLevel" false target="model"}} class='fa fa-lock'></i> <button class="btn" {{action "lockTrustLevel" false target="model"}}>{{i18n 'admin.user.unlock_trust_level'}}</button>
<i title='{{i18n 'admin.user.trust_level_locked_tip'}}' class='fa fa-lock'></i> {{d-button action="lockTrustLevel" actionParam=false label="admin.user.unlock_trust_level"}}
{{else}}
<i title='{{i18n 'admin.user.trust_level_unlocked_tip'}}' class='fa fa-unlock'></i> <button class="btn" {{action "lockTrustLevel" true target="model"}}>{{i18n 'admin.user.lock_trust_level'}}</button>
<i title='{{i18n 'admin.user.trust_level_unlocked_tip'}}' class='fa fa-unlock'></i> {{d-button action="lockTrustLevel" actionParam=true label="admin.user.lock_trust_level"}}
{{/if}}
{{/if}}
{{#if model.tl3Requirements}}
@ -306,18 +275,12 @@
<div class='value'>{{model.isSuspended}}</div>
<div class='controls'>
{{#if model.isSuspended}}
<button class='btn btn-danger' {{action "unsuspend" target="content"}}>
{{fa-icon "ban"}}
{{i18n 'admin.user.unsuspend'}}
</button>
{{d-button class="btn-danger" action="unsuspend" icon="ban" label="admin.user.unsuspend"}}
{{suspendDuration}}
{{i18n 'admin.user.suspended_explanation'}}
{{else}}
{{#if model.canSuspend}}
<button class='btn btn-danger' {{action "showSuspendModal" this}}>
{{fa-icon "ban"}}
{{i18n 'admin.user.suspend'}}
</button>
{{d-button class="btn-danger" action="showSuspendModal" actionParam=model icon="ban" label="admin.user.suspend"}}
{{i18n 'admin.user.suspended_explanation'}}
{{/if}}
{{/if}}
@ -344,16 +307,10 @@
<div class='controls'>
{{#conditional-loading-spinner size="small" condition=model.blockingUser}}
{{#if model.blocked}}
<button class='btn' {{action "unblock" target="content"}}>
{{fa-icon "thumbs-o-up"}}
{{i18n 'admin.user.unblock'}}
</button>
{{d-button action="unblock" icon="thumbs-o-up" label="admin.user.unblock"}}
{{i18n 'admin.user.block_explanation'}}
{{else}}
<button class='btn' {{action "block" target="content"}}>
{{fa-icon "ban"}}
{{i18n 'admin.user.block'}}
</button>
{{d-button action="block" icon="ban" label="admin.user.block"}}
{{i18n 'admin.user.block_explanation'}}
{{/if}}
{{/conditional-loading-spinner}}
@ -423,10 +380,7 @@
<div class='controls'>
{{#if model.can_delete_all_posts}}
{{#if model.post_count}}
<button class='btn btn-danger' {{action "deleteAllPosts" target="content"}}>
{{fa-icon "trash-o"}}
{{i18n 'admin.user.delete_all_posts'}}
</button>
{{d-button class="btn-danger" action="deleteAllPosts" icon="trash-o" label="admin.user.delete_all_posts"}}
{{/if}}
{{else}}
{{model.deleteAllPostsExplanation}}

View File

@ -1,7 +1,10 @@
<div class="dashboard-stats version-check {{if versionCheck.critical_updates 'critical' 'normal'}}">
<table class="table table-condensed table-hover">
{{custom-html name="upgrade-header" versionCheck=versionCheck tagName="thead"}}
</table>
<table class="table table-condensed table-hover">
<thead>
{{custom-html 'upgrade-header'}}
<tr>
<th>&nbsp;</th>
<th>{{i18n 'admin.dashboard.installed_version'}}</th>

View File

@ -89,7 +89,6 @@
//= require_tree ./discourse/models
//= require_tree ./discourse/components
//= require_tree ./discourse/raw-views
//= require_tree ./discourse/views
//= require_tree ./discourse/helpers
//= require_tree ./discourse/templates
//= require_tree ./discourse/routes

View File

@ -18,6 +18,10 @@ RawHandlebars.helpers['get'] = function(context, options) {
var firstContext = options.contexts[0];
var val = firstContext[context];
if (context.indexOf('controller') === 0) {
context = context.replace(/^controller\./, '');
}
if (val && val.isDescriptor) { return Em.get(firstContext, context); }
val = val === undefined ? Em.get(firstContext, context): val;
return val;

View File

@ -10,19 +10,23 @@ export function setResolverOption(name, value) {
_options[name] = value;
}
export function getResolverOption(name) {
return _options[name];
}
function parseName(fullName) {
const nameParts = fullName.split(":"),
type = nameParts[0], fullNameWithoutType = nameParts[1],
name = fullNameWithoutType,
namespace = get(this, 'namespace'),
root = namespace;
const nameParts = fullName.split(":");
const type = nameParts[0];
let fullNameWithoutType = nameParts[1];
const namespace = get(this, 'namespace');
const root = namespace;
return {
fullName: fullName,
type: type,
fullNameWithoutType: fullNameWithoutType,
name: name,
root: root,
fullName,
type,
fullNameWithoutType,
name: fullNameWithoutType,
root,
resolveMethodName: "resolve" + classify(type)
};
}
@ -125,12 +129,21 @@ export function buildResolver(baseName) {
}
},
findConnectorTemplate(parsedName) {
const full = parsedName.fullNameWithoutType.replace('components/', '');
if (full.indexOf('connectors') === 0) {
return Ember.TEMPLATES[`javascripts/${full}`];
}
},
resolveTemplate(parsedName) {
return this.findPluginMobileTemplate(parsedName) ||
this.findPluginTemplate(parsedName) ||
this.findMobileTemplate(parsedName) ||
this.findTemplate(parsedName) ||
this.findLoadingTemplate(parsedName) ||
this.findConnectorTemplate(parsedName) ||
Ember.TEMPLATES.not_found;
},

View File

@ -6,7 +6,7 @@ const _pluginCallbacks = [];
const Discourse = Ember.Application.extend({
rootElement: '#main',
_docTitle: document.title,
__TAGS_INCLUDED__: true,
RAW_TEMPLATES: {},
getURL(url) {
if (!url) return url;
@ -137,7 +137,7 @@ const Discourse = Ember.Application.extend({
Discourse.instanceInitializer({
name: "_discourse_plugin_" + (++initCount),
after: 'inject-objects',
initialize: function() {
initialize() {
withPluginApi(cb.version, cb.code);
}
});

View File

@ -1,5 +1,5 @@
import { on, observes, default as computed } from 'ember-addons/ember-computed-decorators';
import { getOwner } from 'discourse-common/lib/get-owner';
import { findRawTemplate } from 'discourse/lib/raw-templates';
export default Ember.Component.extend({
@computed('placeholderKey')
@ -18,7 +18,6 @@ export default Ember.Component.extend({
var self = this;
var selectedBadges;
var template = getOwner(this).lookup('template:badge-selector-autocomplete.raw');
self.$('input').autocomplete({
allowAny: false,
items: _.isArray(this.get('badgeNames')) ? this.get('badgeNames') : [this.get('badgeNames')],
@ -43,7 +42,7 @@ export default Ember.Component.extend({
});
});
},
template: template
template: findRawTemplate('badge-selector-autocomplete')
});
}
});

View File

@ -1,7 +1,7 @@
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
import Category from 'discourse/models/category';
import { on, observes } from 'ember-addons/ember-computed-decorators';
import { getOwner } from 'discourse-common/lib/get-owner';
import { findRawTemplate } from 'discourse/lib/raw-templates';
export default Ember.Component.extend({
@observes('categories')
@ -13,7 +13,6 @@ export default Ember.Component.extend({
@on('didInsertElement')
_initializeAutocomplete(opts) {
const self = this,
template = getOwner(this).lookup('template:category-selector-autocomplete.raw'),
regexp = new RegExp(`href=['\"]${Discourse.getURL('/c/')}([^'\"]+)`);
this.$('input').autocomplete({
@ -41,7 +40,7 @@ export default Ember.Component.extend({
self.set('categories', categories);
});
},
template,
template: findRawTemplate('category-selector-autocomplete'),
transformComplete(category) {
return categoryBadgeHTML(category, {allowUncategorized: true});
}

View File

@ -6,7 +6,7 @@ import { linkSeenTagHashtags, fetchUnseenTagHashtags } from 'discourse/lib/link-
import { load } from 'pretty-text/oneboxer';
import { ajax } from 'discourse/lib/ajax';
import InputValidation from 'discourse/models/input-validation';
import { getOwner } from 'discourse-common/lib/get-owner';
import { findRawTemplate } from 'discourse/lib/raw-templates';
import { tinyAvatar,
displayErrorForUpload,
getUploadMarkdown,
@ -62,10 +62,9 @@ export default Ember.Component.extend({
@on('didInsertElement')
_composerEditorInit() {
const topicId = this.get('topic.id');
const template = getOwner(this).lookup('template:user-selector-autocomplete.raw');
const $input = this.$('.d-editor-input');
$input.autocomplete({
template,
template: findRawTemplate('user-selector-autocomplete'),
dataSource: term => userSearch({ term, topicId, includeGroups: true }),
key: "@",
transformComplete: v => v.username || v.name
@ -168,7 +167,7 @@ export default Ember.Component.extend({
post.set('refreshedPost', true);
}
$oneboxes.each((_, o) => load(o, refresh, ajax));
$oneboxes.each((_, o) => load(o, refresh, ajax, this.currentUser.id));
},
_warnMentionedGroups($preview) {

View File

@ -31,7 +31,7 @@ export default Ember.Component.extend({
}
},
@observes('composer.titleLength')
@observes('composer.titleLength', 'watchForLink')
_titleChanged() {
if (this.get('composer.titleLength') === 0) { this.set('autoPosted', false); }
if (this.get('autoPosted') || !this.get('watchForLink')) { return; }
@ -51,15 +51,16 @@ export default Ember.Component.extend({
},
_checkForUrl() {
if (!this.element || this.isDestroying || this.isDestroyed) { return; }
if (this.get('isAbsoluteUrl') && (this.get('composer.reply')||"").length === 0) {
// Try to onebox. If success, update post body and title.
this.set('composer.loading', true);
const link = document.createElement('a');
link.href = this.get('composer.title');
let loadOnebox = load(link, false, ajax);
let loadOnebox = load(link, false, ajax, this.currentUser.id, true);
if (loadOnebox && loadOnebox.then) {
loadOnebox.then( () => {

View File

@ -0,0 +1,20 @@
import { getCustomHTML } from 'discourse/helpers/custom-html';
import { getOwner } from 'discourse-common/lib/get-owner';
export default Ember.Component.extend({
init() {
this._super();
const name = this.get('name');
const html = getCustomHTML(name);
if (html) {
this.set('html', html);
this.set('layoutName', 'components/custom-html-container');
} else {
const template = getOwner(this).lookup(`template:${name}`);
if (template) {
this.set('layoutName', name);
}
}
}
});

View File

@ -11,6 +11,7 @@ import { translations } from 'pretty-text/emoji/data';
import { emojiSearch } from 'pretty-text/emoji';
import { emojiUrlFor } from 'discourse/lib/text';
import { getRegister } from 'discourse-common/lib/get-owner';
import { findRawTemplate } from 'discourse/lib/raw-templates';
import deprecated from 'discourse-common/lib/deprecated';
// Our head can be a static string or a function that returns a string
@ -297,11 +298,10 @@ export default Ember.Component.extend({
},
_applyCategoryHashtagAutocomplete() {
const template = this.register.lookup('template:category-tag-autocomplete.raw');
const siteSettings = this.siteSettings;
this.$('.d-editor-input').autocomplete({
template: template,
template: findRawTemplate('category-tag-autocomplete'),
key: '#',
transformComplete(obj) {
if (obj.model) {
@ -323,11 +323,10 @@ export default Ember.Component.extend({
if (!this.siteSettings.enable_emoji) { return; }
const register = this.register;
const template = this.register.lookup('template:emoji-selector-autocomplete.raw');
const self = this;
$editorInput.autocomplete({
template: template,
template: findRawTemplate('emoji-selector-autocomplete'),
key: ":",
afterComplete(text) {
self.set('value', text);
@ -553,6 +552,7 @@ export default Ember.Component.extend({
applySurround: (head, tail, exampleKey, opts) => this._applySurround(selected, head, tail, exampleKey, opts),
applyList: (head, exampleKey) => this._applyList(selected, head, exampleKey),
addText: text => this._addText(selected, text),
replaceText: text => this._addText({pre: '', post: ''}, text),
getText: () => this.get('value'),
};

View File

@ -5,16 +5,10 @@ import { selectedText } from 'discourse/lib/utilities';
import { observes } from 'ember-addons/ember-computed-decorators';
function highlight(postNumber) {
const $contents = $(`#post_${postNumber} .topic-body`),
origColor = $contents.data('orig-color') || $contents.css('backgroundColor');
const $contents = $(`#post_${postNumber} .topic-body`);
$contents.data("orig-color", origColor)
.addClass('highlighted')
.stop()
.animate({ backgroundColor: origColor }, 2500, 'swing', function() {
$contents.removeClass('highlighted');
$contents.css({'background-color': ''});
});
$contents.addClass('highlighted');
$contents.on('animationend', () => $contents.removeClass('highlighted'));
}
export default Ember.Component.extend(AddArchetypeClass, Scrolling, {

View File

@ -0,0 +1,54 @@
import { default as computed } from 'ember-addons/ember-computed-decorators';
import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.Component.extend({
@computed("model.public")
canJoinGroup(publicGroup) {
return !!(this.currentUser) && publicGroup;
},
@computed('model.allow_membership_requests', 'model.alias_level')
canRequestMembership(allowMembershipRequests, aliasLevel) {
return !!(this.currentUser) && allowMembershipRequests && aliasLevel === 99;
},
@computed("model.is_group_user", "model.id", "groupUserIds")
userIsGroupUser(isGroupUser, groupId, groupUserIds) {
if (isGroupUser) {
return isGroupUser;
} else {
return groupUserIds.includes(groupId);
}
},
actions: {
joinGroup() {
this.set('updatingMembership', true);
const model = this.get('model');
model.addMembers(this.currentUser.get('username')).then(() => {
model.set('is_group_user', true);
}).catch(popupAjaxError).finally(() => {
this.set('updatingMembership', false);
});
},
leaveGroup() {
this.set('updatingMembership', true);
const model = this.get('model');
model.removeMember(this.currentUser).then(() => {
model.set('is_group_user', false);
}).catch(popupAjaxError).finally(() => {
this.set('updatingMembership', false);
});
},
requestMembership() {
const groupName = this.get('model.name');
const title = I18n.t('groups.request_membership_pm.title');
const body = I18n.t('groups.request_membership_pm.body', { groupName });
this.sendAction("createNewMessageViaParams", groupName, title, body);
}
}
});

View File

@ -1,5 +1,5 @@
import { on, observes, default as computed } from 'ember-addons/ember-computed-decorators';
import { getOwner } from 'discourse-common/lib/get-owner';
import { findRawTemplate } from 'discourse/lib/raw-templates';
export default Ember.Component.extend({
@computed('placeholderKey')
@ -19,7 +19,6 @@ export default Ember.Component.extend({
var selectedGroups;
var groupNames = this.get('groupNames');
var template = getOwner(this).lookup('template:group-selector-autocomplete.raw');
self.$('input').autocomplete({
allowAny: false,
items: _.isArray(groupNames) ? groupNames : (Ember.isEmpty(groupNames)) ? [] : [groupNames],
@ -44,7 +43,7 @@ export default Ember.Component.extend({
});
});
},
template: template
template: findRawTemplate('group-selector-autocomplete')
});
}
});

View File

@ -14,6 +14,7 @@ export default Ember.Component.extend({
},
tagName: 'ul',
selectedHtml: null,
classNames: ['mobile-nav'],

View File

@ -23,8 +23,6 @@ export default Ember.Component.extend({
this._super();
const name = this.get('widget');
(this.get('delegated') || []).forEach(m => this.set(m, m));
this.register = getRegister(this);
this._widgetClass = queryRegistry(name) || this.register.lookupFactory(`widget:${name}`);
@ -33,7 +31,6 @@ export default Ember.Component.extend({
console.error(`Error: Could not find widget: ${name}`);
}
this._childEvents = [];
this._connected = [];
this._dispatched = [];

View File

@ -0,0 +1,30 @@
import { observes } from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
init() {
this._super();
const connector = this.get('connector');
this.set('layoutName', connector.templateName);
const args = this.get('args') || {};
Object.keys(args).forEach(key => this.set(key, args[key]));
const connectorClass = this.get('connector.connectorClass');
connectorClass.setupComponent.call(this, args, this);
},
@observes('args')
_argsChanged() {
const args = this.get('args') || {};
Object.keys(args).forEach(key => this.set(key, args[key]));
},
send(name, ...args) {
const connectorClass = this.get('connector.connectorClass');
const action = connectorClass.actions[name];
return action ? action.call(this, ...args) : this._super(name, ...args);
}
});

View File

@ -0,0 +1,51 @@
/**
A plugin outlet is an extension point for templates where other templates can
be inserted by plugins.
## Usage
If your handlebars template has:
```handlebars
{{plugin-outlet name="evil-trout"}}
```
Then any handlebars files you create in the `connectors/evil-trout` directory
will automatically be appended. For example:
plugins/hello/assets/javascripts/discourse/templates/connectors/evil-trout/hello.hbs
With the contents:
```handlebars
<b>Hello World</b>
```
Will insert <b>Hello World</b> at that point in the template.
## Disabling
If a plugin returns a disabled status, the outlets will not be wired up for it.
The list of disabled plugins is returned via the `Site` singleton.
**/
import { connectorsFor } from 'discourse/lib/plugin-connectors';
export default Ember.Component.extend({
tagName: 'span',
connectors: null,
init() {
this._super();
const name = this.get('name');
if (name) {
const args = this.get('args');
const connectors = connectorsFor(name).filter(con => {
return con.connectorClass.shouldRender(args, this);
});
this.set('connectors', connectors);
}
}
});

View File

@ -48,12 +48,15 @@ export default Em.Component.extend({
init() {
this._super();
this._init();
this._update();
Ember.run.scheduleOnce('afterRender', () => {
this._init();
this._update();
});
},
@observes('searchTerm')
_updateOptions() {
this._update();
Ember.run.debounce(this, this._update, 250);
},

View File

@ -47,6 +47,12 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
this.queueRerender();
},
willRender() {
if (this.get('currentUser.staff')) {
$('body').addClass('staff');
}
},
didInsertElement() {
this._super();
$(window).on('resize.discourse-menu-panel', () => this.afterRender());

View File

@ -1,17 +1,24 @@
import renderTag from 'discourse/lib/render-tag';
function formatTag(t) {
return renderTag(t.id, {count: t.count});
return renderTag(t.id, {count: t.count, noHref: true});
}
export default Ember.TextField.extend({
classNameBindings: [':tag-chooser'],
attributeBindings: ['tabIndex', 'placeholderKey', 'categoryId'],
_initValue: function() {
init() {
this._super();
const tags = this.get('tags') || [];
this.set('value', tags.join(", "));
}.on('init'),
if (this.get('allowCreate') !== false) {
this.set('allowCreate', this.site.get('can_create_tag'));
}
this.set('termMatchesForbidden', false);
},
_valueChanged: function() {
const tags = this.get('value').split(',').map(v => v.trim()).reject(v => v.length === 0).uniq();
@ -32,18 +39,13 @@ export default Ember.TextField.extend({
}
}.observes('tags'),
_initializeTags: function() {
const site = this.site,
self = this,
filterRegexp = new RegExp(this.site.tags_filter_regexp, "g");
didInsertElement() {
this._super();
var limit = this.siteSettings.max_tags_per_topic;
const self = this;
const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g");
if (this.get('allowCreate') !== false) {
this.set('allowCreate', site.get('can_create_tag'));
}
this.set('termMatchesForbidden', false);
let limit = this.siteSettings.max_tags_per_topic;
if (this.get('unlimitedTagCount')) {
limit = null;
@ -77,7 +79,7 @@ export default Ember.TextField.extend({
callback(data);
},
createSearchChoice: function(term, data) {
createSearchChoice(term, data) {
term = term.replace(filterRegexp, '').trim().toLowerCase();
// No empty terms, make sure the user has permission to create the tag
@ -89,14 +91,14 @@ export default Ember.TextField.extend({
return { id: term, text: term };
}
},
createSearchChoicePosition: function(list, item) {
createSearchChoicePosition(list, item) {
// Search term goes on the bottom
list.push(item);
},
formatSelection: function (data) {
return data ? renderTag(this.text(data)) : undefined;
formatSelection(data) {
return data ? renderTag(this.text(data), {noHref: true}) : undefined;
},
formatSelectionCssClass: function(){
formatSelectionCssClass() {
return "discourse-tag-select2";
},
formatResult: formatTag,
@ -127,10 +129,11 @@ export default Ember.TextField.extend({
}
},
});
}.on('didInsertElement'),
},
_destroyTags: function() {
willDestroyElement() {
this._super();
this.$().select2('destroy');
}.on('willDestroyElement')
}
});

View File

@ -1,17 +1,11 @@
import computed from 'ember-addons/ember-computed-decorators';
import DelegatedActions from 'discourse/mixins/delegated-actions';
export default Ember.Component.extend(DelegatedActions, {
export default Ember.Component.extend({
elementId: 'topic-footer-buttons',
// Allow us to extend it
layoutName: 'components/topic-footer-buttons',
init() {
this._super();
this.delegateAll(this.get('topicDelegated'));
},
@computed('topic.details.can_invite_to')
canInviteTo(result) {
return !this.site.mobileView && result;

View File

@ -1,6 +1,6 @@
import computed from 'ember-addons/ember-computed-decorators';
import { bufferedRender } from 'discourse-common/lib/buffered-render';
import { getOwner } from 'discourse-common/lib/get-owner';
import { findRawTemplate } from 'discourse/lib/raw-templates';
export function showEntrance(e) {
let target = $(e.target);
@ -32,7 +32,7 @@ export default Ember.Component.extend(bufferedRender({
},
buildBuffer(buffer) {
const template = getOwner(this).lookup('template:list/topic-list-item.raw');
const template = findRawTemplate('list/topic-list-item');
if (template) {
buffer.push(template(this));
}
@ -128,14 +128,11 @@ export default Ember.Component.extend(bufferedRender({
highlight(opts = { isLastViewedTopic: false }) {
const $topic = this.$();
const originalCol = $topic.css('backgroundColor');
$topic
.addClass('highlighted')
.attr('data-islastviewedtopic', opts.isLastViewedTopic)
.stop()
.animate({ backgroundColor: originalCol }, 2500, 'swing', function() {
$topic.removeClass('highlighted');
});
.attr('data-islastviewedtopic', opts.isLastViewedTopic);
$topic.on('animationend', () => $topic.removeClass('highlighted'));
},
_highlightIfNeeded: function() {

View File

@ -1,4 +1,5 @@
import { observes } from 'ember-addons/ember-computed-decorators';
import showModal from 'discourse/lib/show-modal';
export default Ember.Component.extend({
composerOpen: null,
@ -93,19 +94,13 @@ export default Ember.Component.extend({
},
keyboardTrigger(e) {
if(e.type === "jump") {
bootbox.prompt(I18n.t('topic.progress.jump_prompt_long'), postIndex => {
if (postIndex === null) { return; }
this.sendAction('jumpToIndex', postIndex);
if (e.type === "jump") {
const controller = showModal('jump-to-post');
controller.setProperties({
topic: this.get('topic'),
postNumber: 1,
jumpToIndex: this.attrs.jumpToIndex
});
// this is insanely hacky, for some reason shown event never fires,
// something is bust in bootbox
// TODO upgrade bootbox to see if this hack can be removed
setTimeout(()=>{
$('.bootbox.modal').trigger('shown');
},50);
}
},

View File

@ -3,17 +3,11 @@ import { default as computed, observes } from 'ember-addons/ember-computed-decor
export default Ember.Component.extend({
elementId: 'topic-progress-wrapper',
classNameBindings: ['docked'],
expanded: false,
docked: false,
progressPosition: null,
postStream: Ember.computed.alias('topic.postStream'),
_streamPercentage: null,
init() {
this._super();
(this.get('delegated') || []).forEach(m => this.set(m, m));
},
@computed('progressPosition')
jumpTopDisabled(progressPosition) {
return progressPosition <= 3;
@ -43,6 +37,15 @@ export default Ember.Component.extend({
}
},
@computed('progressPosition', 'topic.last_read_post_id')
showBackButton(position, lastReadId) {
if (!lastReadId) { return; }
const stream = this.get('postStream.stream');
const readPos = stream.indexOf(lastReadId) || 0;
return (readPos < (stream.length - 1)) && (readPos > position);
},
@observes('postStream.stream.[]')
_updateBar() {
Ember.run.scheduleOnce('afterRender', this, this._updateProgressBar);
@ -146,6 +149,10 @@ export default Ember.Component.extend({
actions: {
toggleExpansion() {
this.toggleProperty('expanded');
},
goBack() {
this.attrs.jumpToPost(this.get('topic.last_read_post_number'));
}
},

View File

@ -1,7 +1,7 @@
import { observes } from 'ember-addons/ember-computed-decorators';
import TextField from 'discourse/components/text-field';
import userSearch from 'discourse/lib/user-search';
import { getOwner } from 'discourse-common/lib/get-owner';
import { findRawTemplate } from 'discourse/lib/raw-templates';
export default TextField.extend({
@observes('usernames')
@ -31,7 +31,7 @@ export default TextField.extend({
}
this.$().val(this.get('usernames')).autocomplete({
template: getOwner(this).lookup('template:user-selector-autocomplete.raw'),
template: findRawTemplate('user-selector-autocomplete'),
disabled: this.get('disabled'),
single: this.get('single'),
allowAny: this.get('allowAny'),

View File

@ -15,4 +15,10 @@ export default Ember.Controller.extend({
loginRequired() {
return Discourse.SiteSettings.login_required && !Discourse.User.current();
},
actions: {
appRouteAction(name) {
return this.send(name);
}
}
});

View File

@ -404,7 +404,6 @@ export default Ember.Controller.extend({
save(force) {
const composer = this.get('model');
const self = this;
// Clear the warning state if we're not showing the checkbox anymore
if (!this.get('showWarning')) {
@ -437,10 +436,10 @@ export default Ember.Controller.extend({
buttons.push({
"label": I18n.t("composer.reply_here") + "<br/><div class='topic-title overflow-ellipsis'>" + currentTopic.get('fancyTitle') + "</div>",
"class": "btn btn-reply-here",
"callback": function() {
callback: () => {
composer.set('topic', currentTopic);
composer.set('post', null);
self.save(true);
this.save(true);
}
});
}
@ -448,9 +447,7 @@ export default Ember.Controller.extend({
buttons.push({
"label": I18n.t("composer.reply_original") + "<br/><div class='topic-title overflow-ellipsis'>" + this.get('model.topic.fancyTitle') + "</div>",
"class": "btn-primary btn-reply-on-original",
"callback": function() {
self.save(true);
}
callback: () => this.save(true)
});
bootbox.dialog(message, buttons, { "classes": "reply-where-modal" });
@ -471,29 +468,32 @@ export default Ember.Controller.extend({
}
});
const promise = composer.save({ imageSizes, editReason: this.get("editReason")}).then(function(result) {
const promise = composer.save({ imageSizes, editReason: this.get("editReason")}).then(result=> {
if (result.responseJson.action === "enqueued") {
self.send('postWasEnqueued', result.responseJson);
self.destroyDraft();
self.close();
self.appEvents.trigger('post-stream:refresh');
this.send('postWasEnqueued', result.responseJson);
this.destroyDraft();
this.close();
this.appEvents.trigger('post-stream:refresh');
return result;
}
// If user "created a new topic/post" or "replied as a new topic" successfully, remove the draft.
if (result.responseJson.action === "create_post" || self.get('replyAsNewTopicDraft')) {
self.destroyDraft();
if (result.responseJson.action === "create_post" || this.get('replyAsNewTopicDraft')) {
this.destroyDraft();
}
if (self.get('model.action') === 'edit') {
self.appEvents.trigger('post-stream:refresh', { id: parseInt(result.responseJson.id) });
if (this.get('model.action') === 'edit') {
this.appEvents.trigger('post-stream:refresh', { id: parseInt(result.responseJson.id) });
if (result.responseJson.post.post_number === 1) {
this.appEvents.trigger('header:show-topic', composer.get('topic'));
}
} else {
self.appEvents.trigger('post-stream:refresh');
this.appEvents.trigger('post-stream:refresh');
}
if (result.responseJson.action === "create_post") {
self.appEvents.trigger('post:highlight', result.payload.post_number);
this.appEvents.trigger('post:highlight', result.payload.post_number);
}
self.close();
this.close();
const currentUser = Discourse.User.current();
if (composer.get('creatingTopic')) {
@ -510,9 +510,9 @@ export default Ember.Controller.extend({
}
}
}).catch(function(error) {
}).catch(error => {
composer.set('disableDrafts', false);
self.appEvents.one('composer:will-open', () => bootbox.alert(error));
this.appEvents.one('composer:will-open', () => bootbox.alert(error));
});
if (this.get('application.currentRouteName').split('.')[0] === 'topic' &&

View File

@ -114,7 +114,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
email = this.get("accountEmail");
if (this.get('rejectedEmails').contains(email)) {
if (this.get('rejectedEmails').includes(email)) {
return InputValidation.create({
failed: true,
reason: I18n.t('user.email.invalid')
@ -314,7 +314,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
});
}
if (this.get('rejectedPasswords').contains(password)) {
if (this.get('rejectedPasswords').includes(password)) {
return InputValidation.create({
failed: true,
reason: I18n.t('user.password.common')

View File

@ -0,0 +1,10 @@
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Controller.extend({
application: Ember.inject.controller(),
@computed('model.is_group_user')
showGroupMessages(isGroupUser) {
return isGroupUser || (this.currentUser && this.currentUser.admin);
}
});

View File

@ -1,6 +1,6 @@
import { popupAjaxError } from 'discourse/lib/ajax-error';
import Group from 'discourse/models/group';
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
import { observes } from 'ember-addons/ember-computed-decorators';
export default Ember.Controller.extend({
queryParams: ['order', 'desc'],
@ -18,16 +18,6 @@ export default Ember.Controller.extend({
this.get('model').findMembers({ order: this.get('order'), desc: this.get('desc') });
},
@computed("model.public")
canJoinGroup(publicGroup) {
return !!(this.currentUser) && publicGroup;
},
@computed('model.allow_membership_requests', 'model.alias_level')
canRequestMembership(allowMembershipRequests, aliasLevel) {
return !!(this.currentUser) && allowMembershipRequests && aliasLevel === 99;
},
actions: {
toggleActions() {
this.toggleProperty("showActions");
@ -44,35 +34,6 @@ export default Ember.Controller.extend({
}
},
requestMembership() {
const groupName = this.get('model.name');
const title = I18n.t('groups.request_membership_pm.title');
const body = I18n.t('groups.request_membership_pm.body', { groupName });
this.transitionToRoute(`/new-message?groupname=${groupName}&title=${title}&body=${body}`);
},
joinGroup() {
this.set('updatingMembership', true);
const model = this.get('model');
model.addMembers(this.currentUser.get('username')).then(() => {
model.set('is_group_user', true);
}).catch(popupAjaxError).finally(() => {
this.set('updatingMembership', false);
});
},
leaveGroup() {
this.set('updatingMembership', true);
const model = this.get('model');
model.removeMember(this.currentUser).then(() => {
model.set('is_group_user', false);
}).catch(popupAjaxError).finally(() => {
this.set('updatingMembership', false);
});
},
loadMore() {
if (this.get("loading")) { return; }
if (this.get("model.members.length") >= this.get("model.user_count")) { return; }

View File

@ -13,21 +13,18 @@ var Tab = Em.Object.extend({
});
export default Ember.Controller.extend({
application: Ember.inject.controller(),
counts: null,
showing: 'members',
tabs: [
Tab.create({ name: 'members', active: true, 'location': 'group.index' }),
Tab.create({ name: 'posts' }),
Tab.create({ name: 'topics' }),
Tab.create({ name: 'mentions' }),
Tab.create({ name: 'messages', requiresMembership: true }),
Tab.create({ name: 'members', 'location': 'group.index', icon: 'users' }),
Tab.create({ name: 'activity' }),
Tab.create({
name: 'edit', i18nKey: 'edit.title',
requiresMembership: true, requiresGroupAdmin: true
name: 'edit', i18nKey: 'edit.title', icon: 'pencil', requiresGroupAdmin: true
}),
Tab.create({
name: 'logs', i18nKey: 'logs.title',
requiresMembership: true, requiresGroupAdmin: true
name: 'logs', i18nKey: 'logs.title', icon: 'list-alt', requiresGroupAdmin: true
})
],
@ -56,31 +53,22 @@ export default Ember.Controller.extend({
this.get('tabs')[0].set('count', this.get('model.user_count'));
},
@observes('showing')
showingChanged() {
const showing = this.get('showing');
this.get('tabs').forEach(tab => {
tab.set('active', showing === tab.get('name'));
});
},
@computed('model.is_group_user', 'model.is_group_owner')
getTabs(isGroupUser, isGroupOwner) {
@computed('model.is_group_user', 'model.is_group_owner', 'model.automatic')
getTabs(isGroupUser, isGroupOwner, automatic) {
return this.get('tabs').filter(t => {
let isMember = false;
let display = true;
if (this.currentUser) {
let admin = this.currentUser.admin;
if (t.get('requiresGroupAdmin')) {
isMember = admin || isGroupOwner;
if (automatic && t.get('requiresGroupAdmin')) {
display = false;
} else {
isMember = admin || isGroupUser;
display = admin || isGroupOwner;
}
}
return isMember || !t.get('requiresMembership');
return display;
});
}
});

View File

@ -0,0 +1,18 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
export default Ember.Controller.extend(ModalFunctionality, {
model: null,
postNumber: null,
actions: {
jump() {
let where = parseInt(this.get('postNumber'));
if (where < 1) { where = 1; }
const max = this.get('topic.postStream.filteredPostsCount');
if (where > max) { where = max; }
this.jumpToIndex(where);
this.send('closeModal');
}
}
});

View File

@ -1,3 +1,5 @@
import TagGroup from 'discourse/models/tag-group';
export default Ember.Controller.extend({
actions: {
selectTagGroup(tagGroup) {
@ -9,8 +11,7 @@ export default Ember.Controller.extend({
},
newTagGroup() {
const newTagGroup = this.store.createRecord('tag-group');
newTagGroup.set('name', I18n.t('tagging.groups.new_name'));
const newTagGroup = TagGroup.create({ id: 'new', name: I18n.t('tagging.groups.new_name') });
this.get('model').pushObject(newTagGroup);
this.send('selectTagGroup', newTagGroup);
}

View File

@ -16,7 +16,7 @@ if (customNavItemHref) {
if (navItem.get('tagId')) {
var name = navItem.get('name');
if ( !Discourse.Site.currentProp('filters').contains(name) ) {
if ( !Discourse.Site.currentProp('filters').includes(name) ) {
return null;
}

View File

@ -32,30 +32,6 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
filter: null,
quoteState: null,
topicDelegated: [
'toggleMultiSelect',
'deleteTopic',
'recoverTopic',
'toggleClosed',
'showAutoClose',
'showFeatureTopic',
'showChangeTimestamp',
'toggleArchived',
'toggleVisibility',
'convertToPublicTopic',
'convertToPrivateMessage',
'jumpTop',
'jumpToPost',
'jumpToPostPrompt',
'jumpToIndex',
'jumpBottom',
'replyToPost',
'toggleArchiveMessage',
'showInvite',
'toggleBookmark',
'showFlagTopic'
],
updateQueryParams() {
const postStream = this.get('model.postStream');
this.setProperties(postStream.get('streamFilters'));
@ -175,6 +151,22 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
actions: {
showPostFlags(post) {
return this.send('showFlags', post);
},
topicRouteAction(name, model) {
return this.send(name, model);
},
openAutoClose() {
this.send('showAutoClose');
},
openFeatureTopic() {
this.send('showFeatureTopic');
},
deselectText() {
this.get('quoteState').setProperties({ buffer: null, postId: null });
},
@ -462,7 +454,15 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
},
jumpToPost(postNumber) {
this._jumpToPostId(this.get('model.postStream').findPostIdForPostNumber(postNumber));
const postStream = this.get('model.postStream');
let postId = postStream.findPostIdForPostNumber(postNumber);
// If we couldn't find the post, find the closest post to it
if (!postId) {
const closest = postStream.closestPostNumberFor(postNumber);
postId = postStream.findPostIdForPostNumber(closest);
}
this._jumpToPostId(postId);
},
jumpTop() {
@ -818,7 +818,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
postSelected(post) {
if (this.get('allPostsSelected')) { return true; }
if (this.get('selectedPosts').contains(post)) { return true; }
if (this.get('selectedPosts').includes(post)) { return true; }
if (this.get('selectedReplies').findBy('post_number', post.get('reply_to_post_number'))) { return true; }
return false;

View File

@ -2,6 +2,9 @@ import { categoryLinkHTML } from 'discourse/helpers/category-link';
import { registerUnbound } from 'discourse-common/lib/helpers';
registerUnbound('category-badge', function(cat, options) {
options.link = false;
return categoryLinkHTML(cat, options);
return categoryLinkHTML(cat, {
hideParent: options.hideParent,
allowUncategorized: options.allowUncategorized,
link: false
});
});

View File

@ -1,5 +1,3 @@
const { registerKeyword } = Ember.__loader.require("ember-htmlbars/keywords");
const { internal } = Ember.__loader.require('htmlbars-runtime');
import PreloadStore from 'preload-store';
let _customizations = {};
@ -24,36 +22,3 @@ export function clearHTMLCache() {
export function setCustomHTML(key, html) {
_customizations[key] = html;
}
registerKeyword('custom-html', {
setupState(state, env, scope, params) {
return { htmlKey: env.hooks.getValue(params[0]) };
},
render(renderNode, env, scope, params, hash, template, inverse, visitor) {
let state = renderNode.getState();
if (!state.htmlKey) { return true; }
const html = getCustomHTML(state.htmlKey);
if (html) {
const htmlHash = { html };
env.hooks.component(renderNode,
env,
scope,
'custom-html-container',
params,
htmlHash,
{ default: template, inverse },
visitor);
return true;
}
template = env.owner.lookup(`template:${state.htmlKey}`);
if (template) {
internal.hostBlock(renderNode, env, scope, template.raw, null, null, visitor, function(options) {
options.templates.template.yield();
});
}
return true;
}
});

View File

@ -1,138 +0,0 @@
/**
A plugin outlet is an extension point for templates where other templates can
be inserted by plugins.
## Usage
If your handlebars template has:
```handlebars
{{plugin-outlet "evil-trout"}}
```
Then any handlebars files you create in the `connectors/evil-trout` directory
will automatically be appended. For example:
plugins/hello/assets/javascripts/discourse/templates/connectors/evil-trout/hello.hbs
With the contents:
```handlebars
<b>Hello World</b>
```
Will insert <b>Hello World</b> at that point in the template.
## Disabling
If a plugin returns a disabled status, the outlets will not be wired up for it.
The list of disabled plugins is returned via the `Site` singleton.
**/
let _connectorCache, _templateCache;
function findOutlets(collection, callback) {
const disabledPlugins = Discourse.Site.currentProp('disabled_plugins') || [];
Object.keys(collection).forEach(function(res) {
if (res.indexOf("/connectors/") !== -1) {
// Skip any disabled plugins
for (let i=0; i<disabledPlugins.length; i++) {
if (res.indexOf("/" + disabledPlugins[i] + "/") !== -1) {
return;
}
}
const segments = res.split("/");
let outletName = segments[segments.length-2];
const uniqueName = segments[segments.length-1];
callback(outletName, res, uniqueName);
}
});
}
export function clearCache() {
_templateCache = null;
_connectorCache = null;
}
function buildConnectorCache() {
_connectorCache = {};
_templateCache = [];
findOutlets(Ember.TEMPLATES, function(outletName, resource, uniqueName) {
_connectorCache[outletName] = _connectorCache[outletName] || [];
_connectorCache[outletName].push({
templateName: resource.replace('javascripts/', ''),
template: Ember.TEMPLATES[resource],
classNames: `${outletName}-outlet ${uniqueName}`
});
});
Object.keys(_connectorCache).forEach(outletName => {
const connector = _connectorCache[outletName];
(connector || []).forEach(s => {
_templateCache.push(s.template);
s.templateId = parseInt(_templateCache.length - 1);
});
});
}
// unbound version of outlets, only has a template
Handlebars.registerHelper('plugin-outlet', function(name) {
if (!_connectorCache) { buildConnectorCache(); }
const connector = _connectorCache[name];
if (connector && connector.length) {
const output = connector.map(c => c.template({context: this}));
return new Handlebars.SafeString(output.join(""));
}
});
const { registerKeyword } = Ember.__loader.require("ember-htmlbars/keywords");
const { internal } = Ember.__loader.require('htmlbars-runtime');
registerKeyword('plugin-outlet', {
setupState(state, env, scope, params) {
if (!_connectorCache) { buildConnectorCache(); }
return { outletName: env.hooks.getValue(params[0]) };
},
render(renderNode, env, scope, params, hash, template, inverse, visitor) {
let state = renderNode.getState();
if (!state.outletName) { return true; }
const connector = _connectorCache[state.outletName];
if (!connector || connector.length === 0) { return true; }
const listTemplate = Ember.TEMPLATES['outlet-list'];
listTemplate.raw.locals = ['templateId', 'outletClasses', 'tagName'];
internal.hostBlock(renderNode, env, scope, listTemplate.raw, null, null, visitor, function(options) {
connector.forEach(source => {
const tid = source.templateId;
options.templates.template.yieldItem(`d-outlet-${tid}`, [
tid,
source.classNames,
hash.tagName || 'div'
]);
});
});
return true;
}
});
registerKeyword('connector', function(morph, env, scope, params, hash, template, inverse, visitor) {
template = _templateCache[parseInt(env.hooks.getValue(hash.templateId))];
env.hooks.component(morph,
env,
scope,
'connector-container',
params,
hash,
{ default: template.raw, inverse },
visitor);
return true;
});

View File

@ -0,0 +1,9 @@
import { connectorsFor } from 'discourse/lib/plugin-connectors';
Handlebars.registerHelper('raw-plugin-outlet', function(args) {
const connectors = connectorsFor(args.hash.name);
if (connectors.length) {
const output = connectors.map(c => c.template({context: this}));
return new Handlebars.SafeString(output.join(""));
}
});

View File

@ -1,8 +1,10 @@
import { registerUnbound } from 'discourse-common/lib/helpers';
import { findRawTemplate } from 'discourse/lib/raw-templates';
let _injections;
function renderRaw(ctx, container, template, templateName, params) {
params = jQuery.extend({}, params);
params.parent = params.parent || ctx;
if (!params.view) {
@ -32,9 +34,9 @@ registerUnbound('raw', function(templateName, params) {
templateName = templateName.replace('.', '/');
const container = Discourse.__container__;
var template = container.lookup('template:' + templateName + '.raw');
const template = findRawTemplate(templateName);
if (!template) {
Ember.warn('Could not find raw template: ' + templateName);
console.warn('Could not find raw template: ' + templateName);
return;
}
return renderRaw(this, container, template, templateName, params);

View File

@ -8,6 +8,18 @@ export default {
withPluginApi('0.1', api => {
api.decorateCooked(highlightSyntax);
api.decorateCooked(lightbox);
api.decorateCooked($elem => {
const players = $('audio', $elem);
if (players.length) {
players.on('play', () => {
const postId = parseInt($elem.closest('article').data('post-id'));
if (postId) {
api.preventCloak(postId);
}
});
}
});
});
}
};

View File

@ -2,6 +2,7 @@ import groups from 'discourse/lib/emoji/groups';
import KeyValueStore from "discourse/lib/key-value-store";
import { emojiList } from 'pretty-text/emoji';
import { emojiUrlFor } from 'discourse/lib/text';
import { findRawTemplate } from 'discourse/lib/raw-templates';
const keyValueStore = new KeyValueStore("discourse_emojis_");
const EMOJI_USAGE = "emojiUsage";
@ -151,7 +152,7 @@ function render(page, offset, options) {
};
$('.emoji-modal', options.appendTo).remove();
const template = options.register.lookup('template:emoji-toolbar.raw');
const template = findRawTemplate('emoji-toolbar');
options.appendTo.append(template(model));
bindEvents(page, offset, options);

View File

@ -5,13 +5,16 @@ const _loading = {};
function loadWithTag(path, cb) {
const head = document.getElementsByTagName('head')[0];
let finished = false;
let s = document.createElement('script');
s.src = path;
if (Ember.Test) { Ember.Test.pendingAjaxRequests++; }
if (Ember.Test) {
Ember.Test.registerWaiter(() => finished);
}
head.appendChild(s);
s.onload = s.onreadystatechange = function(_, abort) {
if (Ember.Test) { Ember.Test.pendingAjaxRequests--; }
finished = true;
if (abort || !s.readyState || s.readyState === "loaded" || s.readyState === "complete") {
s = s.onload = s.onreadystatechange = null;
if (!abort) {

View File

@ -11,6 +11,7 @@ import { preventCloak } from 'discourse/widgets/post-stream';
import { h } from 'virtual-dom';
import { addFlagProperty } from 'discourse/components/site-header';
import { addPopupMenuOptionsCallback } from 'discourse/controllers/composer';
import { extraConnectorClass } from 'discourse/lib/plugin-connectors';
class PluginApi {
constructor(version, container) {
@ -330,15 +331,41 @@ class PluginApi {
addStorePluralization(thing, plural) {
this.container.lookup("store:main").addPluralization(thing, plural);
}
/**
* Register a Connector class for a particular outlet and connector.
*
* For example, if the outlet is `user-profile-primary` and your connector
* template is called `my-connector.hbs`:
*
* ```javascript
* api.registerConnectorClass('user-profile-primary', 'my-connector', {
* shouldRender(args, component) {
* return component.siteSettings.my_plugin_enabled;
* }
* });
* ```
*
* For more information on connector classes, see:
* https://meta.discourse.org/t/important-changes-to-plugin-outlets-for-ember-2-10/54136
**/
registerConnectorClass(outletName, connectorName, klass) {
extraConnectorClass(`${outletName}/${connectorName}`, klass);
}
}
let _pluginv01;
function getPluginApi(version) {
version = parseFloat(version);
if (version <= 0.5) {
if (version <= 0.6) {
if (!_pluginv01) {
_pluginv01 = new PluginApi(version, Discourse.__container__);
}
// We are recycling the compatible object, but let's update to the higher version
if (_pluginv01.version < version) {
_pluginv01.version = version;
}
return _pluginv01;
} else {
console.warn(`Plugin API v${version} is not supported`);

View File

@ -0,0 +1,81 @@
let _connectorCache;
let _extraConnectorClasses = {};
let _classPaths;
export function resetExtraClasses() {
_extraConnectorClasses = {};
_classPaths = undefined;
}
// Note: In plugins, define a class by path and it will be wired up automatically
// eg: discourse/connectors/<OUTLET NAME>/<CONNECTOR NAME>.js.es6
export function extraConnectorClass(name, obj) {
_extraConnectorClasses[name] = obj;
}
const DefaultConnectorClass = {
actions: {},
shouldRender: () => true,
setupComponent() { }
};
function findOutlets(collection, callback) {
const disabledPlugins = Discourse.Site.currentProp('disabled_plugins') || [];
Object.keys(collection).forEach(function(res) {
if (res.indexOf("/connectors/") !== -1) {
// Skip any disabled plugins
for (let i=0; i<disabledPlugins.length; i++) {
if (res.indexOf("/" + disabledPlugins[i] + "/") !== -1) {
return;
}
}
const segments = res.split("/");
let outletName = segments[segments.length-2];
const uniqueName = segments[segments.length-1];
callback(outletName, res, uniqueName);
}
});
}
export function clearCache() {
_connectorCache = null;
}
function findClass(outletName, uniqueName) {
if (!_classPaths) {
_classPaths = {};
findOutlets(require._eak_seen, (outlet, res, un) => {
_classPaths[`${outlet}/${un}`] = require(res).default;
});
}
const id = `${outletName}/${uniqueName}`;
let foundClass = _extraConnectorClasses[id] || _classPaths[id];
return foundClass ?
jQuery.extend({}, DefaultConnectorClass, foundClass) :
DefaultConnectorClass;
}
function buildConnectorCache() {
_connectorCache = {};
findOutlets(Ember.TEMPLATES, function(outletName, resource, uniqueName) {
_connectorCache[outletName] = _connectorCache[outletName] || [];
_connectorCache[outletName].push({
templateName: resource.replace('javascripts/', ''),
template: Ember.TEMPLATES[resource],
classNames: `${outletName}-outlet ${uniqueName}`,
connectorClass: findClass(outletName, uniqueName)
});
});
}
export function connectorsFor(outletName) {
if (!_connectorCache) { buildConnectorCache(); }
return _connectorCache[outletName] || [];
}

View File

@ -0,0 +1,13 @@
import { getResolverOption } from 'discourse-common/resolver';
export function findRawTemplate(name) {
if (getResolverOption('mobileView')) {
return Discourse.RAW_TEMPLATES[`javascripts/mobile/${name}`] ||
Discourse.RAW_TEMPLATES[`javascripts/${name}`] ||
Discourse.RAW_TEMPLATES[`mobile/${name}`] ||
Discourse.RAW_TEMPLATES[name];
}
return Discourse.RAW_TEMPLATES[`javascripts/${name}`] ||
Discourse.RAW_TEMPLATES[name];
}

View File

@ -5,7 +5,7 @@ export default function renderTag(tag, params) {
tag = Handlebars.Utils.escapeExpression(tag);
const classes = ['tag-' + tag, 'discourse-tag'];
const tagName = params.tagName || "a";
const href = tagName === "a" ? " href='" + Discourse.getURL("/tags/" + tag) + "' " : "";
const href = (tagName === "a" && !params.noHref) ? " href='" + Discourse.getURL("/tags/" + tag) + "' " : "";
if (Discourse.SiteSettings.tag_style || params.style) {
classes.push(params.style || Discourse.SiteSettings.tag_style);

View File

@ -11,26 +11,22 @@ export default function(name, opts) {
const controllerName = opts.admin ? `modals/${name}` : name;
const viewClass = container.lookupFactory('view:' + name);
const controller = container.lookup('controller:' + controllerName);
if (viewClass) {
route.render(name, { into: 'modal', outlet: 'modalBody' });
} else {
const templateName = opts.templateName || Ember.String.dasherize(name);
const templateName = opts.templateName || Ember.String.dasherize(name);
const renderArgs = { into: 'modal', outlet: 'modalBody'};
if (controller) { renderArgs.controller = controllerName; }
const renderArgs = { into: 'modal', outlet: 'modalBody'};
if (controller) { renderArgs.controller = controllerName; }
if (opts.addModalBodyView) {
renderArgs.view = 'modal-body';
}
if (opts.addModalBodyView) {
renderArgs.view = 'modal-body';
}
const modalName = `modal/${templateName}`;
const fullName = opts.admin ? `admin/templates/${modalName}` : modalName;
route.render(fullName, renderArgs);
if (opts.title) {
modalController.set('title', I18n.t(opts.title));
}
const modalName = `modal/${templateName}`;
const fullName = opts.admin ? `admin/templates/${modalName}` : modalName;
route.render(fullName, renderArgs);
if (opts.title) {
modalController.set('title', I18n.t(opts.title));
}
if (controller) {

View File

@ -1,8 +1,21 @@
import { defaultHomepage } from 'discourse/lib/utilities';
const rootURL = Discourse.BaseUri;
const BareRouter = Ember.Router.extend({
rootURL,
location: Ember.testing ? 'none': 'discourse-location'
location: Ember.testing ? 'none': 'discourse-location',
handleURL(url) {
const params = url.split('?');
if (params[0] === "/") {
url = defaultHomepage();
if (params[1] && params[1].length) {
url = `${url}?${params[1]}`;
}
}
return this._super(url);
}
});
// Ember's router can't be extended. We need to allow plugins to add routes to routes that were defined
@ -67,7 +80,8 @@ class RouteNode {
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);
console.log(`warning: we can't have duplicate route names anymore`, newOpts);
// router.route(this.name, newOpts, builder);
});
}
}

View File

@ -1,6 +0,0 @@
export default Ember.Mixin.create({
init() {
this._super();
(this.get('delegated') || []).forEach(m => this.set(m, m));
},
});

View File

@ -1,12 +0,0 @@
export default Ember.Mixin.create({
delegateAll(actionNames) {
actionNames = actionNames || [];
this.actions = this.actions || {};
actionNames.forEach(m => {
this.actions[m] = function() { this.sendAction(m); };
this.set(m, m);
});
}
});

View File

@ -97,10 +97,12 @@ const Category = RestModel.extend({
custom_fields: this.get('custom_fields'),
topic_template: this.get('topic_template'),
suppress_from_homepage: this.get('suppress_from_homepage'),
all_topics_wiki: this.get('all_topics_wiki'),
allowed_tags: this.get('allowed_tags'),
allowed_tag_groups: this.get('allowed_tag_groups'),
sort_order: this.get('sort_order'),
sort_ascending: this.get('sort_ascending')
sort_ascending: this.get('sort_ascending'),
topic_featured_link_allowed: this.get('topic_featured_link_allowed')
},
type: id ? 'PUT' : 'POST'
});
@ -169,18 +171,6 @@ 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;
}
}
});

View File

@ -143,6 +143,8 @@ const Composer = RestModel.extend({
if (!this.siteSettings.topic_featured_link_enabled || !canEditTitle || creatingPrivateMessage) { return false; }
const categoryIds = this.site.get('topic_featured_link_allowed_category_ids');
if (!categoryId && categoryIds &&
(categoryIds.indexOf(this.site.get('uncategorized_category_id')) !== -1 || !this.siteSettings.allow_uncategorized_topics)) { return true; }
return categoryIds === undefined || !categoryIds.length || categoryIds.indexOf(categoryId) !== -1;
},
@ -552,8 +554,7 @@ const Composer = RestModel.extend({
post.get('post_number') === 1 &&
this.get('topic.details.can_edit')) {
const topicProps = this.getProperties(Object.keys(_edit_topic_serializer));
promise = Topic.update(this.get('topic'), topicProps);
promise = Topic.update(this.get('topic'), topicProps);
} else {
promise = Ember.RSVP.resolve();
}

View File

@ -86,9 +86,9 @@ NavItem.reopenClass({
testName = name.split("/")[0],
anonymous = !Discourse.User.current();
if (anonymous && !Discourse.Site.currentProp('anonymous_top_menu_items').contains(testName)) return null;
if (anonymous && !Discourse.Site.currentProp('anonymous_top_menu_items').includes(testName)) return null;
if (!Discourse.Category.list() && testName === "categories") return null;
if (!Discourse.Site.currentProp('top_menu_items').contains(testName)) return null;
if (!Discourse.Site.currentProp('top_menu_items').includes(testName)) return null;
var args = { name: name, hasIcon: name === "unread" }, extra = null, self = this;
if (opts.category) { args.category = opts.category; }

View File

@ -168,7 +168,7 @@ export default RestModel.extend({
this.set('summary', false);
let jump = false;
if (userFilters.contains(username)) {
if (userFilters.includes(username)) {
userFilters.removeObject(username);
} else {
userFilters.addObject(username);
@ -256,7 +256,7 @@ export default RestModel.extend({
return this.findPostsByIds(gap).then(posts => {
posts.forEach(p => {
const stored = this.storePost(p);
if (!currentPosts.contains(stored)) {
if (!currentPosts.includes(stored)) {
currentPosts.insertAt(postIdx++, stored);
}
});
@ -410,7 +410,7 @@ export default RestModel.extend({
if (stored) {
const posts = this.get('posts');
if (!posts.contains(stored)) {
if (!posts.includes(stored)) {
if (!this.get('loadingBelow')) {
this.get('postsWithPlaceholders').appendPost(() => posts.pushObject(stored));
} else {

View File

@ -9,9 +9,10 @@ const TagGroup = RestModel.extend({
},
save() {
var url = "/tag_groups",
self = this;
if (this.get('id')) {
let url = "/tag_groups";
const self = this,
isNew = this.get('id') === 'new';
if (!isNew) {
url = "/tag_groups/" + this.get('id');
}
@ -25,9 +26,11 @@ const TagGroup = RestModel.extend({
parent_tag_name: this.get('parent_tag_name') ? this.get('parent_tag_name') : undefined,
one_per_topic: this.get('one_per_topic')
},
type: this.get('id') ? 'PUT' : 'POST'
type: isNew ? 'POST' : 'PUT'
}).then(function(result) {
if(result.id) { self.set('id', result.id); }
if(result.tag_group && result.tag_group.id) {
self.set('id', result.tag_group.id);
}
self.set('savingStatus', I18n.t('saved'));
self.set('saving', false);
});

View File

@ -254,7 +254,7 @@ const User = RestModel.extend({
// TODO: We can remove this when migrated fully to rest model.
this.set('isSaving', true);
return ajax(`/users/${this.get('username_lower')}`, {
return ajax(`/users/${this.get('username_lower')}.json`, {
data: data,
type: 'PUT'
}).then(result => {

View File

@ -17,7 +17,7 @@ function inject() {
}
function injectAll(app, name) {
inject(app, name, 'controller', 'component', 'route', 'view', 'model', 'adapter');
inject(app, name, 'controller', 'component', 'route', 'model', 'adapter');
}
export default {

View File

@ -1,5 +1,3 @@
import { defaultHomepage } from 'discourse/lib/utilities';
export default function() {
// Error page
this.route('exception', { path: '/exception' });
@ -45,19 +43,20 @@ export default function() {
this.route('categoryNone', { path: '/c/:slug/none' });
this.route('category', { path: '/c/:parentSlug/:slug' });
this.route('categoryWithID', { path: '/c/:parentSlug/:slug/:id' });
// homepage
this.route(defaultHomepage(), { path: '/' });
});
this.route('groups', { resetNamespace: true });
this.route('group', { path: '/groups/:name', resetNamespace: true }, function() {
this.route('members');
this.route('posts');
this.route('topics');
this.route('mentions');
this.route('messages');
this.route('activity', function() {
this.route('posts');
this.route('topics');
this.route('mentions');
this.route('messages');
});
this.route('logs');
this.route('edit');
});

View File

@ -114,7 +114,8 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
const model = this.store.createRecord('category', {
color: "AB9364", text_color: "FFFFFF", group_permissions: [{group_name: everyoneName, permission_type: 1}],
available_groups: groups.map(g => g.name),
allow_badges: true
allow_badges: true,
topic_featured_link_allowed: true
});
showModal("edit-category", { model });

View File

@ -11,7 +11,7 @@ export default Discourse.Route.extend(OpenComposer, {
},
beforeModel(transition) {
if (transition.intent.url === "/" &&
if ((transition.intent.url === "/" || transition.intent.url === "/categories") &&
transition.targetName.indexOf("discovery.top") === -1 &&
Discourse.User.currentProp("should_be_redirected_to_top")) {
Discourse.User.currentProp("should_be_redirected_to_top", false);

View File

@ -0,0 +1,3 @@
import { buildGroupPage } from 'discourse/routes/group-activity-posts';
export default buildGroupPage('mentions');

View File

@ -0,0 +1,3 @@
import { buildGroupPage } from 'discourse/routes/group-activity-posts';
export default buildGroupPage('messages');

View File

@ -11,12 +11,12 @@ export function buildGroupPage(type) {
},
setupController(controller, model) {
this.controllerFor('group-posts').setProperties({ model, type });
this.controllerFor('group-activity-posts').setProperties({ model, type });
this.controllerFor("group").set("showing", type);
},
renderTemplate() {
this.render('group-posts');
this.render('group-activity-posts');
},
actions: {

View File

@ -0,0 +1,3 @@
import { buildGroupPage } from 'discourse/routes/group-activity-posts';
export default buildGroupPage('topics');

View File

@ -0,0 +1,5 @@
export default Ember.Route.extend({
beforeModel: function() {
this.transitionTo("group.activity.posts");
}
});

View File

@ -1,3 +0,0 @@
import { buildGroupPage } from 'discourse/routes/group-posts';
export default buildGroupPage('mentions');

View File

@ -1,3 +0,0 @@
import { buildGroupPage } from 'discourse/routes/group-posts';
export default buildGroupPage('messages');

View File

@ -1,3 +0,0 @@
import { buildGroupPage } from 'discourse/routes/group-posts';
export default buildGroupPage('topics');

View File

@ -1,6 +1,6 @@
export default Discourse.Route.extend({
titleToken() {
return I18n.t('groups.index');
return I18n.t('groups.index.title');
},
model(params) {

View File

@ -1 +0,0 @@
{{!-- THIS IS AN EMPTY TEMPLATE THAT NEEDS TO BE OVERWRITTEN --}}

View File

@ -1,17 +1,17 @@
{{plugin-outlet "above-site-header"}}
{{plugin-outlet name="above-site-header"}}
{{site-header canSignUp=canSignUp
showCreateAccount="showCreateAccount"
showLogin="showLogin"
showKeyboard="showKeyboardShortcutsHelp"
toggleMobileView="toggleMobileView"
toggleAnonymous="toggleAnonymous"
logout="logout"}}
{{plugin-outlet "below-site-header"}}
showCreateAccount=(action "appRouteAction" "showCreateAccount")
showLogin=(action "appRouteAction" "showLogin")
showKeyboard=(action "appRouteAction" "showKeyboardShortcutsHelp")
toggleMobileView=(action "appRouteAction" "toggleMobileView")
toggleAnonymous=(action "appRouteAction" "toggleAnonymous")
logout=(action "appRouteAction" "logout")}}
{{plugin-outlet name="below-site-header"}}
<div id="main-outlet" class="wrap">
<div class="container">
{{#if showTop}}
{{custom-html "top"}}
{{custom-html name="top"}}
{{/if}}
{{global-notice}}
{{create-topics-notice}}
@ -20,11 +20,11 @@
{{outlet "user-card"}}
</div>
{{plugin-outlet "above-footer"}}
{{plugin-outlet name="above-footer"}}
{{#if showFooter}}
{{custom-html "footer"}}
{{custom-html name="footer"}}
{{/if}}
{{plugin-outlet "below-footer"}}
{{plugin-outlet name="below-footer"}}
{{outlet "modal"}}
{{topic-entrance}}

View File

@ -8,6 +8,6 @@
{{tag-drop firstCategory=firstCategory secondCategory=secondCategory tagId=tagId}}
{{/if}}
{{plugin-outlet "bread-crumbs-right" tagName="li"}}
{{plugin-outlet name="bread-crumbs-right" connectorTagName="li"}}
<div class='clear'></div>

View File

@ -19,11 +19,18 @@
</label>
</section>
<section class="field">
<label>
{{input type="checkbox" checked=category.all_topics_wiki}}
{{i18n "category.all_topics_wiki"}}
</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}}
{{input type="checkbox" checked=category.topic_featured_link_allowed}}
{{i18n 'category.topic_featured_link_allowed'}}
</label>
</div>
@ -56,7 +63,7 @@
</label>
</section>
{{plugin-outlet "category-email-in"}}
{{plugin-outlet name="category-email-in" args=(hash category=category)}}
{{/if}}
{{#if showPositionInput}}
@ -82,4 +89,4 @@
</section>
{{/unless}}
{{plugin-outlet "category-custom-settings"}}
{{plugin-outlet name="category-custom-settings" args=(hash category=category)}}

View File

@ -0,0 +1,32 @@
{{#if canJoinGroup}}
{{#if userIsGroupUser}}
{{d-button action="leaveGroup"
class="btn-danger group-index-leave"
icon="minus"
label="groups.leave"
disabled=updatingMembership}}
{{else}}
{{d-button action="joinGroup"
class="group-index-join"
icon="plus"
label="groups.join"
disabled=updatingMembership}}
{{/if}}
{{else if canRequestMembership}}
{{#if userIsGroupUser}}
{{#if showMembershipStatus}}
{{d-button
class="btn-primary"
icon="user"
label="groups.is_group_user"
disabled=true}}
{{/if}}
{{else}}
{{d-button action="requestMembership"
class="group-index-request"
icon="envelope"
label="groups.request"}}
{{/if}}
{{else}}
{{yield}}
{{/if}}

View File

@ -1,5 +1,5 @@
{{#each navItems as |navItem|}}
{{navigation-item content=navItem filterMode=filterMode}}
{{/each}}
{{custom-html "extraNavItem"}}
{{plugin-outlet "extra-nav-item" tagName="li"}}
{{custom-html name="extraNavItem"}}
{{plugin-outlet name="extra-nav-item" connectorTagName="li"}}

View File

@ -0,0 +1,3 @@
{{#each connectors as |c|}}
{{plugin-connector connector=c args=args class=c.classNames tagName=connectorTagName}}
{{/each}}

View File

@ -6,7 +6,7 @@
<a href={{item.postUrl}}>{{{item.title}}}</a>
</span>
<div class="category">{{category-link item.category}}</div>
{{plugin-outlet "user-stream-item-header"}}
{{plugin-outlet name="user-stream-item-header" args=(hash item=item)}}
</div>
{{#if actionDescription}}

View File

@ -14,4 +14,4 @@
{{topic-featured-link topic}}
{{/if}}
</div>
{{plugin-outlet "topic-category"}}
{{plugin-outlet name="topic-category" args=(hash topic=topic category=topic.category)}}

View File

@ -1,5 +1,18 @@
{{#if showAdminButton}}
{{topic-admin-menu-button topic=topic delegated=topicDelegated openUpwards="true"}}
{{topic-admin-menu-button
topic=topic
openUpwards="true"
toggleMultiSelect=toggleMultiSelect
deleteTopic=deleteTopic
recoverTopic=recoverTopic
toggleClosed=toggleClosed
toggleArchived=toggleArchived
toggleVisibility=toggleVisibility
showAutoClose=showAutoClose
showFeatureTopic=showFeatureTopic
showChangeTimestamp=showChangeTimestamp
convertToPublicTopic=convertToPublicTopic
convertToPrivateMessage=convertToPrivateMessage}}
{{/if}}
{{#unless topic.isPrivateMessage}}
@ -10,7 +23,7 @@
title=bookmarkTitle
label=bookmarkLabel
icon="bookmark"
action="toggleBookmark"}}
action=toggleBookmark}}
<button class="btn share" data-share-url={{topic.shareUrl}} title={{i18n "topic.share.help"}}>
{{fa-icon "link"}}
@ -22,7 +35,7 @@
title="topic.flag_topic.help"
label="topic.flag_topic.title"
icon="flag"
action="showFlagTopic"}}
action=showFlagTopic}}
{{/if}}
{{/if}}
@ -33,7 +46,7 @@
title="topic.invite_reply.help"
label="topic.invite_reply.title"
icon="users"
action="showInvite"
action=showInvite
disabled=inviteDisabled}}
{{/if}}
@ -42,19 +55,25 @@
title=archiveTitle
label=archiveLabel
icon=archiveIcon
action="toggleArchiveMessage"}}
action=toggleArchiveMessage}}
{{/if}}
{{plugin-outlet "topic-footer-main-buttons-before-create" tagName="span"}}
{{#if topic.details.can_create_post}}
{{d-button class="btn-primary create"
icon="reply"
action="replyToPost"
action=replyToPost
label="topic.reply.title"
title="topic.reply.help"}}
{{/if}}
{{plugin-outlet "after-topic-footer-main-buttons" tagName="span"}}
{{plugin-outlet name="after-topic-footer-main-buttons"
args=(hash topic=topic)
connectorTagName="span"}}
{{pinned-button topic=topic topicDelegated=topicDelegated}}
{{topic-notifications-button topic=topic topicDelegated=topicDelegated}}
{{plugin-outlet "after-topic-footer-buttons" tagName="span"}}
{{pinned-button topic=topic}}
{{topic-notifications-button topic=topic}}
{{plugin-outlet name="after-topic-footer-buttons"
args=(hash topic=topic)
connectorTagName="span"}}

View File

@ -1,8 +1,13 @@
{{#if showBackButton}}
<div class='progress-back-container'>
{{d-button label="topic.timeline.back" class="btn-primary progress-back" action="goBack" icon="arrow-down"}}
</div>
{{/if}}
<nav id='topic-progress' title="{{i18n 'topic.progress.title'}}" class="{{if hideProgress 'hidden'}}">
<div class='nums'>
<h4>{{progressPosition}}</h4><span class="{{if hugeNumberOfPosts 'hidden'}}">
<span>/</span>
<h4>{{postStream.filteredPostsCount}}</h4></span>
</div>
<i class="fa {{unless expanded 'fa-sort'}}"></i>
</nav>

View File

@ -30,7 +30,7 @@
<h2>{{user.title}}</h2>
{{/if}}
{{plugin-outlet "user-card-post-names"}}
{{plugin-outlet name="user-card-post-names" args=(hash user=user)}}
</span>
</div>
@ -85,7 +85,7 @@
</span>
{{/if}}
{{plugin-outlet "user-card-location-and-website"}}
{{plugin-outlet name="user-card-location-and-website" args=(hash user=user)}}
</div>
{{/if}}
@ -95,7 +95,7 @@
<h3><span class='desc'>{{i18n 'last_post'}}</span> {{format-date user.last_posted_at leaveAgo="true"}}</h3>
{{/if}}
<h3><span class='desc'>{{i18n 'joined'}}</span> {{format-date user.created_at leaveAgo="true"}}</h3>
{{plugin-outlet "user-card-metadata"}}
{{plugin-outlet name="user-card-metadata" args=(hash user=user)}}
</div>
{{/if}}

View File

@ -30,7 +30,7 @@
{{#if model.viewOpen}}
<div class='control-row reply-area'>
<div class='composer-fields'>
{{plugin-outlet "composer-open"}}
{{plugin-outlet name="composer-open" args=(hash model=model)}}
<div class='reply-to'>
{{{model.actionTitle}}}
@ -78,11 +78,10 @@
{{#if model.archetype.hasOptions}}
<button class='btn' {{action "showOptions"}}>{{i18n 'topic.options'}}</button>
{{/if}}
{{render "additional-composer-buttons" model}}
{{/if}}
</div>
{{/if}}
{{plugin-outlet "composer-fields"}}
{{plugin-outlet name="composer-fields" args=(hash model=model)}}
</div>
{{composer-editor topic=topic
@ -104,7 +103,7 @@
{{#if currentUser}}
<div class='submit-panel'>
{{plugin-outlet "composer-fields-below"}}
{{plugin-outlet name="composer-fields-below" args=(hash model=model)}}
{{#if canEditTags}}
{{tag-chooser tags=model.tags tabIndex="4" categoryId=model.categoryId}}
{{/if}}

View File

@ -21,11 +21,12 @@
<div class="row">
<div class="full-width">
<div id="list-area">
{{plugin-outlet "discovery-list-container-top"}}
{{plugin-outlet name="discovery-list-container-top"
args=(hash category=category)}}
{{outlet "list-container"}}
</div>
</div>
</div>
</div>
{{plugin-outlet "discovery-below"}}
{{plugin-outlet name="discovery-below"}}

View File

@ -94,7 +94,7 @@
{{#each result.topic.tags as |tag|}}
{{discourse-tag tag}}
{{/each}}
{{plugin-outlet "full-page-search-category"}}
{{plugin-outlet name="full-page-search-category" args=(hash result=result)}}
</div>
</div>

View File

@ -1,24 +1,6 @@
{{#if model.members}}
{{#if canJoinGroup}}
{{#if model.is_group_user}}
{{d-button action="leaveGroup"
class="btn-danger group-index-leave"
icon="minus"
label="groups.leave"
disabled=updatingMembership}}
{{else}}
{{d-button action="joinGroup"
class="group-index-join"
icon="plus"
label="groups.join"
disabled=updatingMembership}}
{{/if}}
{{else if canRequestMembership}}
{{d-button action="requestMembership"
class="group-index-request"
icon="envelope"
label="groups.request"}}
{{/if}}
{{group-membership-button model=model
createNewMessageViaParams='createNewMessageViaParams'}}
{{#load-more selector=".group-members tr" action="loadMore"}}
<table class='group-members'>

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