Version bump
This commit is contained in:
commit
a4ae8570ea
2
Gemfile
2
Gemfile
@ -25,8 +25,6 @@ else
|
||||
gem 'seed-fu', '~> 2.3.3'
|
||||
end
|
||||
|
||||
gem 'actionpack-action_caching'
|
||||
|
||||
# Rails 4.1.6+ will relax the mail gem version requirement to `~> 2.5, >= 2.5.4`.
|
||||
# However, mail gem 2.6.x currently does not work with discourse because of the
|
||||
# reference to `Mail::RFC2822Parser` in `lib/email.rb`. This ensure discourse
|
||||
|
||||
@ -15,8 +15,6 @@ GEM
|
||||
activesupport (= 4.1.10)
|
||||
rack (~> 1.5.2)
|
||||
rack-test (~> 0.6.2)
|
||||
actionpack-action_caching (1.1.1)
|
||||
actionpack (>= 4.0.0, < 5.0)
|
||||
actionview (4.1.10)
|
||||
activesupport (= 4.1.10)
|
||||
builder (~> 3.1)
|
||||
@ -208,7 +206,7 @@ GEM
|
||||
omniauth-twitter (1.0.1)
|
||||
multi_json (~> 1.3)
|
||||
omniauth-oauth (~> 1.0)
|
||||
onebox (1.5.20)
|
||||
onebox (1.5.21)
|
||||
moneta (~> 0.7)
|
||||
multi_json (~> 1.7)
|
||||
mustache (~> 0.99)
|
||||
@ -392,7 +390,6 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
actionpack-action_caching
|
||||
active_model_serializers (~> 0.8.3)
|
||||
annotate
|
||||
aws-sdk
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
|
||||
/**
|
||||
This controller supports interface for creating custom CSS skins in Discourse.
|
||||
|
||||
@ -21,6 +23,10 @@ export default Ember.ArrayController.extend({
|
||||
this.set('selectedItem', item);
|
||||
},
|
||||
|
||||
importModal: function() {
|
||||
showModal('upload-customization');
|
||||
},
|
||||
|
||||
/**
|
||||
Select a given style
|
||||
|
||||
|
||||
@ -4,24 +4,26 @@ export default Ember.ObjectController.extend({
|
||||
viewingBarChart: Em.computed.equal('viewMode', 'barChart'),
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
categoryId: null,
|
||||
refreshing: false,
|
||||
|
||||
actions: {
|
||||
refreshReport: function() {
|
||||
var self = this;
|
||||
this.set('refreshing', true);
|
||||
Discourse.Report.find(this.get('type'), this.get('startDate'), this.get('endDate')).then(function(r) {
|
||||
self.set('model', r);
|
||||
}).finally(function() {
|
||||
self.set('refreshing', false);
|
||||
});
|
||||
refreshReport() {
|
||||
this.set("refreshing", true);
|
||||
Discourse.Report.find(
|
||||
this.get("type"),
|
||||
this.get("startDate"),
|
||||
this.get("endDate"),
|
||||
this.get("categoryId")
|
||||
).then(m => this.set("model", m)
|
||||
).finally(() => this.set("refreshing", false));
|
||||
},
|
||||
|
||||
viewAsTable: function() {
|
||||
viewAsTable() {
|
||||
this.set('viewMode', 'table');
|
||||
},
|
||||
|
||||
viewAsBarChart: function() {
|
||||
viewAsBarChart() {
|
||||
this.set('viewMode', 'barChart');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
Discourse.Report = Discourse.Model.extend({
|
||||
import round from "discourse/lib/round";
|
||||
|
||||
const Report = Discourse.Model.extend({
|
||||
reportUrl: function() {
|
||||
return("/admin/reports/" + this.get('type'));
|
||||
}.property('type'),
|
||||
|
||||
valueAt: function(numDaysAgo) {
|
||||
valueAt(numDaysAgo) {
|
||||
if (this.data) {
|
||||
var wantedDate = moment().subtract(numDaysAgo, 'days').format('YYYY-MM-DD');
|
||||
var item = this.data.find( function(d) { return d.x === wantedDate; } );
|
||||
@ -14,7 +16,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||
return 0;
|
||||
},
|
||||
|
||||
sumDays: function(startDaysAgo, endDaysAgo) {
|
||||
sumDays(startDaysAgo, endDaysAgo) {
|
||||
if (this.data) {
|
||||
var earliestDate = moment().subtract(endDaysAgo, 'days').startOf('day');
|
||||
var latestDate = moment().subtract(startDaysAgo, 'days').startOf('day');
|
||||
@ -25,7 +27,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||
sum += datum.y;
|
||||
}
|
||||
});
|
||||
return sum;
|
||||
return round(sum, -2);
|
||||
}
|
||||
},
|
||||
|
||||
@ -100,7 +102,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||
}
|
||||
}.property('type'),
|
||||
|
||||
percentChangeString: function(val1, val2) {
|
||||
percentChangeString(val1, val2) {
|
||||
var val = ((val1 - val2) / val2) * 100;
|
||||
if( isNaN(val) || !isFinite(val) ) {
|
||||
return null;
|
||||
@ -111,7 +113,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||
}
|
||||
},
|
||||
|
||||
changeTitle: function(val1, val2, prevPeriodString) {
|
||||
changeTitle(val1, val2, prevPeriodString) {
|
||||
var title = '';
|
||||
var percentChange = this.percentChangeString(val1, val2);
|
||||
if( percentChange ) {
|
||||
@ -139,26 +141,29 @@ Discourse.Report = Discourse.Model.extend({
|
||||
|
||||
});
|
||||
|
||||
Discourse.Report.reopenClass({
|
||||
find: function(type, startDate, endDate) {
|
||||
Report.reopenClass({
|
||||
|
||||
return Discourse.ajax("/admin/reports/" + type, {data: {
|
||||
start_date: startDate,
|
||||
end_date: endDate
|
||||
}}).then(function (json) {
|
||||
find(type, startDate, endDate, categoryId) {
|
||||
return Discourse.ajax("/admin/reports/" + type, {
|
||||
data: {
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
category_id: categoryId
|
||||
}
|
||||
}).then(json => {
|
||||
// Add a percent field to each tuple
|
||||
var maxY = 0;
|
||||
json.report.data.forEach(function (row) {
|
||||
let maxY = 0;
|
||||
json.report.data.forEach(row => {
|
||||
if (row.y > maxY) maxY = row.y;
|
||||
});
|
||||
if (maxY > 0) {
|
||||
json.report.data.forEach(function (row) {
|
||||
row.percentage = Math.round((row.y / maxY) * 100);
|
||||
});
|
||||
json.report.data.forEach(row => row.percentage = Math.round((row.y / maxY) * 100));
|
||||
}
|
||||
var model = Discourse.Report.create({type: type});
|
||||
const model = Discourse.Report.create({ type: type });
|
||||
model.setProperties(json.report);
|
||||
return model;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default Report;
|
||||
@ -78,13 +78,18 @@ Discourse.SiteCustomization = Discourse.Model.extend({
|
||||
siteCustomization.set('savingStatus', I18n.t('saved'));
|
||||
siteCustomization.set('saving',false);
|
||||
siteCustomization.startTrackingChanges();
|
||||
return siteCustomization;
|
||||
});
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (!this.id) return;
|
||||
return Discourse.ajax("/admin/site_customizations/" + this.id, { type: 'DELETE' });
|
||||
}
|
||||
},
|
||||
|
||||
download_url: function() {
|
||||
return Discourse.getURL('/admin/site_customizations/' + this.id);
|
||||
}.property('id')
|
||||
});
|
||||
|
||||
var SiteCustomizations = Ember.ArrayProxy.extend({
|
||||
|
||||
@ -12,8 +12,9 @@ export default Discourse.Route.extend({
|
||||
if (versionChecks) {
|
||||
c.set('versionCheck', Discourse.VersionCheck.create(d.version_check));
|
||||
}
|
||||
_.each(d.reports,function(report){
|
||||
c.set(report.type, Discourse.Report.create(report));
|
||||
|
||||
['global_reports', 'page_view_reports', 'private_message_reports', 'http_reports', 'user_reports'].forEach(name => {
|
||||
c.set(name, d[name].map(r => Discourse.Report.create(r)));
|
||||
});
|
||||
|
||||
var topReferrers = d.top_referrers;
|
||||
|
||||
@ -8,12 +8,14 @@
|
||||
<button {{action "newCustomization"}} class='btn'>
|
||||
{{fa-icon "plus"}}{{i18n 'admin.customize.new'}}
|
||||
</button>
|
||||
{{d-button action="importModal" icon="upload" label="admin.customize.import"}}
|
||||
</div>
|
||||
|
||||
{{#if selectedItem}}
|
||||
<div {{bind-attr class=":current-style view.maximized:maximized"}}>
|
||||
<div class='wrapper'>
|
||||
{{text-field class="style-name" value=selectedItem.name}}
|
||||
<a class="btn export" download target="_blank" href={{selectedItem.download_url}}>{{fa-icon "download"}} {{i18n 'admin.export_json.button_text'}}</a>
|
||||
|
||||
<div class='admin-controls'>
|
||||
<ul class="nav nav-pills">
|
||||
|
||||
@ -17,7 +17,9 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#unless loading}}
|
||||
{{admin-report-trust-level-counts report=users_by_trust_level}}
|
||||
{{#each r in user_reports}}
|
||||
{{admin-report-trust-level-counts report=r}}
|
||||
{{/each}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -26,15 +28,15 @@
|
||||
<div class="dashboard-stats totals">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="title"><i class='fa fa-shield'></i> {{i18n 'admin.dashboard.admins'}}</td>
|
||||
<td class="title">{{fa-icon "shield"}} {{i18n 'admin.dashboard.admins'}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'admins'}}{{admins}}{{/link-to}}</td>
|
||||
<td class="title"><i class='fa fa-ban'></i> {{i18n 'admin.dashboard.suspended'}}</td>
|
||||
<td class="title">{{fa-icon "ban"}} {{i18n 'admin.dashboard.suspended'}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'suspended'}}{{suspended}}{{/link-to}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title"><i class='fa fa-shield'></i> {{i18n 'admin.dashboard.moderators'}}</td>
|
||||
<td class="title">{{fa-icon "shield"}} {{i18n 'admin.dashboard.moderators'}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'moderators'}}{{moderators}}{{/link-to}}</td>
|
||||
<td class="title"><i class='fa fa-ban'></i> {{i18n 'admin.dashboard.blocked'}}</td>
|
||||
<td class="title">{{fa-icon "ban"}} {{i18n 'admin.dashboard.blocked'}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'blocked'}}{{blocked}}{{/link-to}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
@ -54,14 +56,9 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#unless loading}}
|
||||
{{admin-report-counts report=visits}}
|
||||
{{admin-report-counts report=signups}}
|
||||
{{admin-report-counts report=topics}}
|
||||
{{admin-report-counts report=posts}}
|
||||
{{admin-report-counts report=likes}}
|
||||
{{admin-report-counts report=flags}}
|
||||
{{admin-report-counts report=bookmarks}}
|
||||
{{admin-report-counts report=emails}}
|
||||
{{#each r in global_reports}}
|
||||
{{admin-report-counts report=r}}
|
||||
{{/each}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -81,21 +78,19 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#unless loading}}
|
||||
{{admin-report-counts report=page_view_anon_reqs}}
|
||||
{{admin-report-counts report=page_view_logged_in_reqs}}
|
||||
{{admin-report-counts report=page_view_crawler_reqs}}
|
||||
{{admin-report-counts report=page_view_total_reqs}}
|
||||
{{#each r in page_view_reports}}
|
||||
{{admin-report-counts report=r}}
|
||||
{{/each}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="dashboard-stats">
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="title" title="{{i18n 'admin.dashboard.private_messages_title'}}"><i class="fa fa-envelope"></i> {{i18n 'admin.dashboard.private_messages_short'}}</th>
|
||||
<th class="title" title="{{i18n 'admin.dashboard.private_messages_title'}}">{{fa-icon "enveloppe"}} {{i18n 'admin.dashboard.private_messages_short'}}</th>
|
||||
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
|
||||
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
|
||||
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
|
||||
@ -105,11 +100,9 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#unless loading}}
|
||||
{{admin-report-counts report=user_to_user_private_messages}}
|
||||
{{admin-report-counts report=system_private_messages}}
|
||||
{{admin-report-counts report=notify_moderators_private_messages}}
|
||||
{{admin-report-counts report=notify_user_private_messages}}
|
||||
{{admin-report-counts report=moderator_warning_private_messages}}
|
||||
{{#each r in private_message_reports}}
|
||||
{{admin-report-counts report=r}}
|
||||
{{/each}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -154,12 +147,9 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#unless loading}}
|
||||
{{admin-report-counts report=http_2xx_reqs}}
|
||||
{{admin-report-counts report=http_3xx_reqs}}
|
||||
{{admin-report-counts report=http_4xx_reqs}}
|
||||
{{admin-report-counts report=http_5xx_reqs}}
|
||||
{{admin-report-counts report=http_background_reqs}}
|
||||
{{admin-report-counts report=http_total_reqs}}
|
||||
{{#each r in http_reports}}
|
||||
{{admin-report-counts report=r}}
|
||||
{{/each}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -176,7 +166,7 @@
|
||||
|
||||
{{#if foundProblems}}
|
||||
<div class="dashboard-stats detected-problems">
|
||||
<div class="look-here"><i class="fa fa-exclamation-triangle"></i></div>
|
||||
<div class="look-here">{{fa-icon "exclamation-triangle"}}</div>
|
||||
<div class="problem-messages">
|
||||
<p {{bind-attr class="loadingProblems:invisible"}}>
|
||||
{{i18n 'admin.dashboard.problems_found'}}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
<div>
|
||||
{{i18n 'admin.dashboard.reports.start_date'}} {{input type="date" value=startDate}}
|
||||
{{i18n 'admin.dashboard.reports.end_date'}} {{input type="date" value=endDate}}
|
||||
{{category-chooser valueAttribute="id" value=categoryId source=categoryId}}
|
||||
{{d-button action="refreshReport" class="btn-primary" label="admin.dashboard.reports.refresh_report" icon="refresh"}}
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{{value-list value=buffered.value addKey="admin.site_settings.add_url"}}
|
||||
{{value-list values=buffered.value addKey="admin.site_settings.add_url"}}
|
||||
{{setting-validation-message message=validationMessage}}
|
||||
<div class='desc'>{{{unbound setting.description}}}</div>
|
||||
|
||||
@ -2,8 +2,8 @@ import RestAdapter from 'discourse/adapters/rest';
|
||||
|
||||
export default function buildPluginAdapter(pluginName) {
|
||||
return RestAdapter.extend({
|
||||
pathFor(store, type) {
|
||||
return "/admin/plugins/" + pluginName + this._super(store, type);
|
||||
pathFor(store, type, findArgs) {
|
||||
return "/admin/plugins/" + pluginName + this._super(store, type, findArgs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// A breadcrumb including category drop downs
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['category-breadcrumb'],
|
||||
classNameBindings: ['hidden:hidden',':category-breadcrumb'],
|
||||
tagName: 'ol',
|
||||
parentCategory: Em.computed.alias('category.parentCategory'),
|
||||
|
||||
@ -12,6 +12,10 @@ export default Ember.Component.extend({
|
||||
return !c.get('parentCategory');
|
||||
}),
|
||||
|
||||
hidden: function(){
|
||||
return Discourse.Mobile.mobileView && !this.get('category');
|
||||
}.property('category'),
|
||||
|
||||
firstCategory: function() {
|
||||
return this.get('parentCategory') || this.get('category');
|
||||
}.property('parentCategory', 'category'),
|
||||
@ -29,6 +33,11 @@ export default Ember.Component.extend({
|
||||
return this.get('categories').filter(function (c) {
|
||||
return c.get('parentCategory') === firstCategory;
|
||||
});
|
||||
}.property('firstCategory', 'hideSubcategories')
|
||||
}.property('firstCategory', 'hideSubcategories'),
|
||||
|
||||
render: function(buffer) {
|
||||
if (this.get('hidden')) { return; }
|
||||
this._super(buffer);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -14,8 +14,12 @@ export default Ember.Component.extend({
|
||||
return result;
|
||||
},
|
||||
|
||||
realNameProperty: function() {
|
||||
return this.get('nameProperty') || 'name';
|
||||
}.property('nameProperty'),
|
||||
|
||||
render(buffer) {
|
||||
const nameProperty = this.get('nameProperty') || 'name',
|
||||
const nameProperty = this.get('realNameProperty'),
|
||||
none = this.get('none');
|
||||
|
||||
// Add none option if required
|
||||
@ -64,16 +68,22 @@ export default Ember.Component.extend({
|
||||
o.selected = !!$(o).attr('selected');
|
||||
});
|
||||
|
||||
// observer for item names changing (optional)
|
||||
if (this.get('nameChanges')) {
|
||||
this.addObserver('content.@each.' + this.get('realNameProperty'), this.rerender);
|
||||
}
|
||||
|
||||
$elem.select2({formatResult: this.comboTemplate, minimumResultsForSearch: 5, width: 'resolve'});
|
||||
|
||||
const castInteger = this.get('castInteger');
|
||||
$elem.on("change", function (e) {
|
||||
let val = $(e.target).val();
|
||||
if (val.length && castInteger) {
|
||||
if (val && val.length && castInteger) {
|
||||
val = parseInt(val, 10);
|
||||
}
|
||||
self.set('value', val);
|
||||
});
|
||||
$elem.trigger('change');
|
||||
}.on('didInsertElement'),
|
||||
|
||||
_destroyDropdown: function() {
|
||||
|
||||
@ -0,0 +1,103 @@
|
||||
|
||||
export default Em.Component.extend({
|
||||
fileInput: null,
|
||||
loading: false,
|
||||
expectedRootObjectName: null,
|
||||
hover: 0,
|
||||
|
||||
classNames: ['json-uploader'],
|
||||
|
||||
_initialize: function() {
|
||||
const $this = this.$();
|
||||
const self = this;
|
||||
|
||||
const $fileInput = $this.find('#js-file-input');
|
||||
this.set('fileInput', $fileInput[0]);
|
||||
|
||||
$fileInput.on('change', function() {
|
||||
self.fileSelected(this.files);
|
||||
});
|
||||
|
||||
$this.on('dragover', function(e) {
|
||||
if (e.preventDefault) e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
$this.on('dragenter', function(e) {
|
||||
if (e.preventDefault) e.preventDefault();
|
||||
self.set('hover', self.get('hover') + 1);
|
||||
return false;
|
||||
});
|
||||
$this.on('dragleave', function(e) {
|
||||
if (e.preventDefault) e.preventDefault();
|
||||
self.set('hover', self.get('hover') - 1);
|
||||
return false;
|
||||
});
|
||||
$this.on('drop', function(e) {
|
||||
if (e.preventDefault) e.preventDefault();
|
||||
|
||||
self.set('hover', 0);
|
||||
self.fileSelected(e.dataTransfer.files);
|
||||
return false;
|
||||
});
|
||||
|
||||
}.on('didInsertElement'),
|
||||
|
||||
accept: function() {
|
||||
return ".json,application/json,application/x-javascript,text/json" + (this.get('extension') ? "," + this.get('extension') : "");
|
||||
}.property('extension'),
|
||||
|
||||
setReady: function() {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(this.get('value'));
|
||||
} catch (e) {
|
||||
this.set('ready', false);
|
||||
return;
|
||||
}
|
||||
|
||||
const rootObject = parsed[this.get('expectedRootObjectName')];
|
||||
|
||||
if (rootObject !== null && rootObject !== undefined) {
|
||||
this.set('ready', true);
|
||||
} else {
|
||||
this.set('ready', false);
|
||||
}
|
||||
}.observes('destination', 'expectedRootObjectName'),
|
||||
|
||||
actions: {
|
||||
selectFile: function() {
|
||||
const $fileInput = $(this.get('fileInput'));
|
||||
$fileInput.click();
|
||||
}
|
||||
},
|
||||
|
||||
fileSelected(fileList) {
|
||||
const self = this;
|
||||
let files = [];
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
files[i] = fileList[i];
|
||||
}
|
||||
const fileNameRegex = /\.(json|txt)$/;
|
||||
files = files.filter(function(file) {
|
||||
if (fileNameRegex.test(file.name)) {
|
||||
return true;
|
||||
}
|
||||
if (file.type === "text/plain") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const firstFile = fileList[0];
|
||||
|
||||
this.set('loading', true);
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(evt) {
|
||||
self.set('value', evt.target.result);
|
||||
self.set('loading', false);
|
||||
};
|
||||
|
||||
reader.readAsText(firstFile);
|
||||
}
|
||||
|
||||
});
|
||||
@ -0,0 +1,56 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'ul',
|
||||
classNameBindings: [':nav', ':nav-pills'],
|
||||
id: 'navigation-bar',
|
||||
selectedNavItem: function(){
|
||||
const filterMode = this.get('filterMode'),
|
||||
navItems = this.get('navItems');
|
||||
|
||||
var item = navItems.find(function(item){
|
||||
return item.get('filterMode').indexOf(filterMode) === 0;
|
||||
});
|
||||
|
||||
return item || navItems[0];
|
||||
}.property('filterMode'),
|
||||
|
||||
closedNav: function(){
|
||||
if (!this.get('expanded')) {
|
||||
this.ensureDropClosed();
|
||||
}
|
||||
}.observes('expanded'),
|
||||
|
||||
ensureDropClosed: function(){
|
||||
if (!this.get('expanded')) {
|
||||
this.set('expanded',false);
|
||||
}
|
||||
$(window).off('click.navigation-bar');
|
||||
Discourse.URL.appEvents.off('dom:clean', this, this.ensureDropClosed);
|
||||
},
|
||||
|
||||
actions: {
|
||||
toggleDrop: function(){
|
||||
this.set('expanded', !this.get('expanded'));
|
||||
var self = this;
|
||||
if (this.get('expanded')) {
|
||||
|
||||
Discourse.URL.appEvents.on('dom:clean', this, this.ensureDropClosed);
|
||||
|
||||
Em.run.next(function() {
|
||||
|
||||
if (!self.get('expanded')) { return; }
|
||||
|
||||
self.$('.drop a').on('click', function(){
|
||||
self.$('.drop').hide();
|
||||
self.set('expanded', false);
|
||||
return true;
|
||||
});
|
||||
|
||||
$(window).on('click.navigation-bar', function() {
|
||||
self.set('expanded', false);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -24,25 +24,13 @@ export default Ember.Component.extend(StringBuffer, {
|
||||
this.get('filterMode').indexOf(this.get('content.filterMode')) === 0;
|
||||
}.property('content.filterMode', 'filterMode'),
|
||||
|
||||
name: function() {
|
||||
var categoryName = this.get('content.categoryName'),
|
||||
name = this.get('content.name'),
|
||||
extra = { count: this.get('content.count') || 0 };
|
||||
|
||||
if (categoryName) {
|
||||
name = 'category';
|
||||
extra.categoryName = Discourse.Formatter.toTitleCase(categoryName);
|
||||
}
|
||||
return I18n.t("filters." + name.replace("/", ".") + ".title", extra);
|
||||
}.property('content.{categoryName,name,count}'),
|
||||
|
||||
renderString(buffer) {
|
||||
const content = this.get('content');
|
||||
buffer.push("<a href='" + content.get('href') + "'>");
|
||||
if (content.get('hasIcon')) {
|
||||
buffer.push("<span class='" + content.get('name') + "'></span>");
|
||||
}
|
||||
buffer.push(this.get('name'));
|
||||
buffer.push(this.get('content.displayName'));
|
||||
buffer.push("</a>");
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export default Ember.Component.extend({
|
||||
classNameBindings: [':gap', 'gap::hidden'],
|
||||
classNameBindings: [':gap', ':jagged-border', 'gap::hidden'],
|
||||
|
||||
initGaps: function(){
|
||||
this.set('loading', false);
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
import SearchResult from 'discourse/components/search-result';
|
||||
export default SearchResult.extend();
|
||||
@ -0,0 +1,2 @@
|
||||
import SearchResult from 'discourse/components/search-result';
|
||||
export default SearchResult.extend();
|
||||
@ -0,0 +1,2 @@
|
||||
import SearchResult from 'discourse/components/search-result';
|
||||
export default SearchResult.extend();
|
||||
@ -0,0 +1,2 @@
|
||||
import SearchResult from 'discourse/components/search-result';
|
||||
export default SearchResult.extend();
|
||||
@ -0,0 +1,11 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'ul',
|
||||
|
||||
_highlightOnInsert: function() {
|
||||
const term = this.get('controller.term');
|
||||
if(!_.isEmpty(term)) {
|
||||
this.$('.blurb').highlight(term.split(/\s+/), {className: 'search-highlight'});
|
||||
this.$('.topic-title').highlight(term.split(/\s+/), {className: 'search-highlight'} );
|
||||
}
|
||||
}.on('didInsertElement')
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import TextField from 'discourse/components/text-field';
|
||||
|
||||
export default TextField.extend({
|
||||
placeholder: function() {
|
||||
return this.get('searchContextEnabled') ? "" : I18n.t('search.title');
|
||||
}.property('searchContextEnabled')
|
||||
});
|
||||
@ -332,7 +332,7 @@ export default Ember.ObjectController.extend(Presence, {
|
||||
this.set('similarTopicsMessage', message);
|
||||
}
|
||||
|
||||
this.store.find('topic', {similar: {title, raw: body}}).then(function(newTopics) {
|
||||
this.store.find('similar-topic', {title, raw: body}).then(function(newTopics) {
|
||||
similarTopics.clear();
|
||||
similarTopics.pushObjects(newTopics.get('content'));
|
||||
|
||||
|
||||
@ -21,8 +21,7 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
maxUsernameLength: Discourse.computed.setting('max_username_length'),
|
||||
minUsernameLength: Discourse.computed.setting('min_username_length'),
|
||||
|
||||
resetForm: function() {
|
||||
|
||||
resetForm() {
|
||||
// We wrap the fields in a structure so we can assign a value
|
||||
this.setProperties({
|
||||
accountName: '',
|
||||
@ -49,11 +48,11 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
if (this.get('passwordValidation.failed')) return true;
|
||||
|
||||
// Validate required fields
|
||||
var userFields = this.get('userFields');
|
||||
let userFields = this.get('userFields');
|
||||
if (userFields) { userFields = userFields.filterProperty('field.required'); }
|
||||
if (!Ember.isEmpty(userFields)) {
|
||||
var anyEmpty = userFields.any(function(uf) {
|
||||
var val = uf.get('value');
|
||||
const anyEmpty = userFields.any(function(uf) {
|
||||
const val = uf.get('value');
|
||||
return !val || Ember.isEmpty(val);
|
||||
});
|
||||
if (anyEmpty) { return true; }
|
||||
@ -61,6 +60,9 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
return false;
|
||||
}.property('passwordRequired', 'nameValidation.failed', 'emailValidation.failed', 'usernameValidation.failed', 'passwordValidation.failed', 'formSubmitted', 'userFields.@each.value'),
|
||||
|
||||
|
||||
usernameRequired: Ember.computed.not('authOptions.omit_username'),
|
||||
|
||||
passwordRequired: function() {
|
||||
return this.blank('authOptions.auth_provider');
|
||||
}.property('authOptions.auth_provider'),
|
||||
@ -89,7 +91,7 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
// Check the email address
|
||||
emailValidation: function() {
|
||||
// If blank, fail without a reason
|
||||
var email;
|
||||
let email;
|
||||
if (this.blank('accountEmail')) {
|
||||
return Discourse.InputValidation.create({
|
||||
failed: true
|
||||
@ -149,7 +151,7 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
}.observes('emailValidation', 'accountEmail'),
|
||||
|
||||
fetchExistingUsername: Discourse.debounce(function() {
|
||||
var self = this;
|
||||
const self = this;
|
||||
Discourse.User.checkUsername(null, this.get('accountEmail')).then(function(result) {
|
||||
if (result.suggestion && (self.blank('accountUsername') || self.get('accountUsername') === self.get('authOptions.username'))) {
|
||||
self.set('accountUsername', result.suggestion);
|
||||
@ -225,7 +227,7 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
},
|
||||
|
||||
checkUsernameAvailability: Discourse.debounce(function() {
|
||||
var _this = this;
|
||||
const _this = this;
|
||||
if (this.shouldCheckUsernameMatch()) {
|
||||
return Discourse.User.checkUsername(this.get('accountUsername'), this.get('accountEmail')).then(function(result) {
|
||||
_this.set('globalNicknameExists', false);
|
||||
@ -275,30 +277,23 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
|
||||
// Actually wait for the async name check before we're 100% sure we're good to go
|
||||
usernameValidation: function() {
|
||||
var basicValidation, uniqueUsername;
|
||||
basicValidation = this.get('basicUsernameValidation');
|
||||
uniqueUsername = this.get('uniqueUsernameValidation');
|
||||
if (uniqueUsername) {
|
||||
return uniqueUsername;
|
||||
}
|
||||
return basicValidation;
|
||||
const basicValidation = this.get('basicUsernameValidation');
|
||||
const uniqueUsername = this.get('uniqueUsernameValidation');
|
||||
return uniqueUsername ? uniqueUsername : basicValidation;
|
||||
}.property('uniqueUsernameValidation', 'basicUsernameValidation'),
|
||||
|
||||
usernameNeedsToBeValidatedWithEmail: function() {
|
||||
usernameNeedsToBeValidatedWithEmail() {
|
||||
return( this.get('globalNicknameExists') || false );
|
||||
},
|
||||
|
||||
// Validate the password
|
||||
passwordValidation: function() {
|
||||
var password;
|
||||
if (!this.get('passwordRequired')) {
|
||||
return Discourse.InputValidation.create({
|
||||
ok: true
|
||||
});
|
||||
return Discourse.InputValidation.create({ ok: true });
|
||||
}
|
||||
|
||||
// If blank, fail without a reason
|
||||
password = this.get("accountPassword");
|
||||
const password = this.get("accountPassword");
|
||||
if (this.blank('accountPassword')) {
|
||||
return Discourse.InputValidation.create({ failed: true });
|
||||
}
|
||||
@ -339,8 +334,8 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
});
|
||||
}.property('accountPassword', 'rejectedPasswords.@each', 'accountUsername', 'accountEmail'),
|
||||
|
||||
fetchConfirmationValue: function() {
|
||||
var createAccountController = this;
|
||||
fetchConfirmationValue() {
|
||||
const createAccountController = this;
|
||||
return Discourse.ajax('/users/hp.json').then(function (json) {
|
||||
createAccountController.set('accountPasswordConfirm', json.value);
|
||||
createAccountController.set('accountChallenge', json.challenge.split("").reverse().join(""));
|
||||
@ -348,12 +343,12 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
},
|
||||
|
||||
actions: {
|
||||
externalLogin: function(provider) {
|
||||
externalLogin(provider) {
|
||||
this.get('controllers.login').send('externalLogin', provider);
|
||||
},
|
||||
|
||||
createAccount: function() {
|
||||
var self = this,
|
||||
createAccount() {
|
||||
const self = this,
|
||||
attrs = this.getProperties('accountName', 'accountEmail', 'accountPassword', 'accountUsername', 'accountPasswordConfirm', 'accountChallenge'),
|
||||
userFields = this.get('userFields');
|
||||
|
||||
@ -369,7 +364,7 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
return Discourse.User.createAccount(attrs).then(function(result) {
|
||||
if (result.success) {
|
||||
// Trigger the browser's password manager using the hidden static login form:
|
||||
var $hidden_login_form = $('#hidden-login-form');
|
||||
const $hidden_login_form = $('#hidden-login-form');
|
||||
$hidden_login_form.find('input[name=username]').val(attrs.accountUsername);
|
||||
$hidden_login_form.find('input[name=password]').val(attrs.accountPassword);
|
||||
$hidden_login_form.find('input[name=redirect]').val(Discourse.getURL('/users/account-created'));
|
||||
@ -397,7 +392,7 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
_createUserFields: function() {
|
||||
if (!this.site) { return; }
|
||||
|
||||
var userFields = this.site.get('user_fields');
|
||||
let userFields = this.site.get('user_fields');
|
||||
if (userFields) {
|
||||
userFields = userFields.map(function(f) {
|
||||
return Ember.Object.create({
|
||||
|
||||
@ -7,7 +7,8 @@ export var queryParams = {
|
||||
status: { replace: true },
|
||||
state: { replace: true },
|
||||
search: { replace: true },
|
||||
max_posts: { replace: true }
|
||||
max_posts: { replace: true },
|
||||
q: { replace: true }
|
||||
};
|
||||
|
||||
// Basic controller options
|
||||
|
||||
@ -2,7 +2,7 @@ import DiscoveryController from 'discourse/controllers/discovery';
|
||||
import { queryParams } from 'discourse/controllers/discovery-sortable';
|
||||
import BulkTopicSelection from 'discourse/mixins/bulk-topic-selection';
|
||||
|
||||
var controllerOpts = {
|
||||
const controllerOpts = {
|
||||
needs: ['discovery'],
|
||||
period: null,
|
||||
|
||||
@ -16,20 +16,42 @@ var controllerOpts = {
|
||||
expandGloballyPinned: false,
|
||||
expandAllPinned: false,
|
||||
|
||||
isSearch: Em.computed.equal('model.filter', 'search'),
|
||||
|
||||
searchTerm: function(){
|
||||
return this.get('model.params.q');
|
||||
}.property('isSearch,model.params,model'),
|
||||
|
||||
actions: {
|
||||
|
||||
changeSort: function(sortBy) {
|
||||
if (sortBy === this.get('order')) {
|
||||
this.toggleProperty('ascending');
|
||||
changeSort(sortBy) {
|
||||
if (this.get('isSearch')) {
|
||||
let term = this.get('searchTerm');
|
||||
let order;
|
||||
|
||||
if (sortBy === 'activity') { order = 'latest'; }
|
||||
if (sortBy === 'views') { order = 'views'; }
|
||||
|
||||
if (order && term.indexOf("order:" + order) === -1) {
|
||||
term = term.replace(/order:[a-z]+/, '');
|
||||
term = term.trim() + " order:" + order;
|
||||
this.set('model.params.q', term);
|
||||
this.get('model').refreshSort();
|
||||
}
|
||||
|
||||
} else {
|
||||
this.setProperties({ order: sortBy, ascending: false });
|
||||
if (sortBy === this.get('order')) {
|
||||
this.toggleProperty('ascending');
|
||||
} else {
|
||||
this.setProperties({ order: sortBy, ascending: false });
|
||||
}
|
||||
this.get('model').refreshSort(sortBy, this.get('ascending'));
|
||||
}
|
||||
this.get('model').refreshSort(sortBy, this.get('ascending'));
|
||||
},
|
||||
|
||||
// Show newly inserted topics
|
||||
showInserted: function() {
|
||||
var tracker = Discourse.TopicTrackingState.current();
|
||||
const tracker = Discourse.TopicTrackingState.current();
|
||||
|
||||
// Move inserted into topics
|
||||
this.get('content').loadBefore(tracker.get('newIncoming'));
|
||||
@ -38,7 +60,7 @@ var controllerOpts = {
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
var filter = this.get('model.filter'),
|
||||
const filter = this.get('model.filter'),
|
||||
self = this;
|
||||
|
||||
this.setProperties({ order: 'default', ascending: false });
|
||||
@ -57,7 +79,7 @@ var controllerOpts = {
|
||||
self.setProperties({ model: list });
|
||||
self.resetSelected();
|
||||
|
||||
var tracking = Discourse.TopicTrackingState.current();
|
||||
const tracking = Discourse.TopicTrackingState.current();
|
||||
if (tracking) {
|
||||
tracking.sync(list, filter);
|
||||
}
|
||||
@ -68,7 +90,7 @@ var controllerOpts = {
|
||||
|
||||
|
||||
resetNew: function() {
|
||||
var self = this;
|
||||
const self = this;
|
||||
|
||||
Discourse.TopicTrackingState.current().resetNew();
|
||||
Discourse.Topic.resetNew().then(function() {
|
||||
@ -113,11 +135,11 @@ var controllerOpts = {
|
||||
footerMessage: function() {
|
||||
if (!this.get('allLoaded')) { return; }
|
||||
|
||||
var category = this.get('category');
|
||||
const category = this.get('category');
|
||||
if( category ) {
|
||||
return I18n.t('topics.bottom.category', {category: category.get('name')});
|
||||
} else {
|
||||
var split = (this.get('model.filter') || '').split('/');
|
||||
const split = (this.get('model.filter') || '').split('/');
|
||||
if (this.get('model.topics.length') === 0) {
|
||||
return I18n.t("topics.none." + split[0], {
|
||||
category: split[1]
|
||||
@ -133,7 +155,7 @@ var controllerOpts = {
|
||||
footerEducation: function() {
|
||||
if (!this.get('allLoaded') || this.get('model.topics.length') > 0 || !Discourse.User.current()) { return; }
|
||||
|
||||
var split = (this.get('model.filter') || '').split('/');
|
||||
const split = (this.get('model.filter') || '').split('/');
|
||||
|
||||
if (split[0] !== 'new' && split[0] !== 'unread') { return; }
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
|
||||
actions: {
|
||||
login: function() {
|
||||
var self = this;
|
||||
const self = this;
|
||||
|
||||
if(this.blank('loginName') || this.blank('loginPassword')){
|
||||
self.flash(I18n.t('login.blank_username_or_password'), 'error');
|
||||
@ -69,14 +69,15 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
sentTo: result.sent_to_email,
|
||||
currentEmail: result.current_email
|
||||
});
|
||||
} else {
|
||||
self.flash(result.error, 'error');
|
||||
}
|
||||
self.flash(result.error, 'error');
|
||||
} else {
|
||||
self.set('loggedIn', true);
|
||||
// Trigger the browser's password manager using the hidden static login form:
|
||||
var $hidden_login_form = $('#hidden-login-form');
|
||||
var destinationUrl = $.cookie('destination_url');
|
||||
var shouldRedirectToUrl = self.session.get("shouldRedirectToUrl");
|
||||
const $hidden_login_form = $('#hidden-login-form');
|
||||
const destinationUrl = $.cookie('destination_url');
|
||||
const shouldRedirectToUrl = self.session.get("shouldRedirectToUrl");
|
||||
$hidden_login_form.find('input[name=username]').val(self.get('loginName'));
|
||||
$hidden_login_form.find('input[name=password]').val(self.get('loginPassword'));
|
||||
if (self.get('loginRequired') && destinationUrl) {
|
||||
@ -102,22 +103,22 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
},
|
||||
|
||||
externalLogin: function(loginMethod){
|
||||
var name = loginMethod.get("name");
|
||||
var customLogin = loginMethod.get("customLogin");
|
||||
const name = loginMethod.get("name");
|
||||
const customLogin = loginMethod.get("customLogin");
|
||||
|
||||
if(customLogin){
|
||||
customLogin();
|
||||
} else {
|
||||
this.set('authenticate', name);
|
||||
var left = this.get('lastX') - 400;
|
||||
var top = this.get('lastY') - 200;
|
||||
const left = this.get('lastX') - 400;
|
||||
const top = this.get('lastY') - 200;
|
||||
|
||||
var height = loginMethod.get("frameHeight") || 400;
|
||||
var width = loginMethod.get("frameWidth") || 800;
|
||||
var w = window.open(Discourse.getURL("/auth/" + name), "_blank",
|
||||
const height = loginMethod.get("frameHeight") || 400;
|
||||
const width = loginMethod.get("frameWidth") || 800;
|
||||
const w = window.open(Discourse.getURL("/auth/" + name), "_blank",
|
||||
"menubar=no,status=no,height=" + height + ",width=" + width + ",left=" + left + ",top=" + top);
|
||||
var self = this;
|
||||
var timer = setInterval(function() {
|
||||
const self = this;
|
||||
const timer = setInterval(function() {
|
||||
if(!w || w.closed) {
|
||||
clearInterval(timer);
|
||||
self.set('authenticate', null);
|
||||
@ -127,10 +128,10 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
},
|
||||
|
||||
createAccount: function() {
|
||||
var createAccountController = this.get('controllers.createAccount');
|
||||
const createAccountController = this.get('controllers.createAccount');
|
||||
if (createAccountController) {
|
||||
createAccountController.resetForm();
|
||||
var loginName = this.get('loginName');
|
||||
const loginName = this.get('loginName');
|
||||
if (loginName && loginName.indexOf('@') > 0) {
|
||||
createAccountController.set("accountEmail", loginName);
|
||||
} else {
|
||||
@ -141,7 +142,7 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
},
|
||||
|
||||
forgotPassword: function() {
|
||||
var forgotPasswordController = this.get('controllers.forgotPassword');
|
||||
const forgotPasswordController = this.get('controllers.forgotPassword');
|
||||
if (forgotPasswordController) { forgotPasswordController.set("accountEmailOrUsername", this.get("loginName")); }
|
||||
this.send("showForgotPassword");
|
||||
}
|
||||
@ -149,13 +150,13 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
|
||||
authMessage: (function() {
|
||||
if (this.blank('authenticate')) return "";
|
||||
var method = Discourse.get('LoginMethod.all').findProperty("name", this.get("authenticate"));
|
||||
const method = Discourse.get('LoginMethod.all').findProperty("name", this.get("authenticate"));
|
||||
if(method){
|
||||
return method.get('message');
|
||||
}
|
||||
}).property('authenticate'),
|
||||
|
||||
authenticationComplete: function(options) {
|
||||
authenticationComplete(options) {
|
||||
|
||||
const self = this;
|
||||
function loginError(errorMsg, className) {
|
||||
@ -187,12 +188,12 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
return;
|
||||
}
|
||||
|
||||
var createAccountController = this.get('controllers.createAccount');
|
||||
const createAccountController = this.get('controllers.createAccount');
|
||||
createAccountController.setProperties({
|
||||
accountEmail: options.email,
|
||||
accountUsername: options.username,
|
||||
accountName: options.name,
|
||||
authOptions: Em.Object.create(options)
|
||||
authOptions: Ember.Object.create(options)
|
||||
});
|
||||
showModal('createAccount');
|
||||
}
|
||||
|
||||
@ -13,13 +13,13 @@ export default DiscourseController.extend({
|
||||
|
||||
isSearch: Em.computed.equal('filterMode', 'search'),
|
||||
|
||||
searchTerm: Em.computed.alias('controllers.discovery/topics.model.params.search'),
|
||||
searchTerm: Em.computed.alias('controllers.discovery/topics.model.params.q'),
|
||||
|
||||
actions: {
|
||||
search: function(){
|
||||
var discovery = this.get('controllers.discovery/topics');
|
||||
var model = discovery.get('model');
|
||||
discovery.set('search', this.get("searchTerm"));
|
||||
discovery.set('q', this.get('searchTerm'));
|
||||
model.refreshSort();
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,10 @@ import DiscourseController from 'discourse/controllers/controller';
|
||||
export default DiscourseController.extend(ModalFunctionality, {
|
||||
emailSent: false,
|
||||
|
||||
onShow() {
|
||||
this.set("emailSent", false);
|
||||
},
|
||||
|
||||
actions: {
|
||||
sendActivationEmail: function() {
|
||||
Discourse.ajax('/users/action/send_activation_email', {data: {username: this.get('username')}, type: 'POST'});
|
||||
|
||||
@ -88,7 +88,13 @@ export default Em.Controller.extend(Presence, {
|
||||
|
||||
actions: {
|
||||
moreOfType: function(type) {
|
||||
this.set('typeFilter', type);
|
||||
if (type === 'topic' && (!this.get('searchContextEnabled') || this.get('searchContext.type') !== 'topic')) {
|
||||
var term = this.get('term');
|
||||
// TODO in topic and in category special handling
|
||||
Discourse.URL.routeTo("/search?q=" + encodeURIComponent(term));
|
||||
} else {
|
||||
this.set('typeFilter', type);
|
||||
}
|
||||
},
|
||||
|
||||
cancelType: function() {
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
notReady: Em.computed.not('ready'),
|
||||
|
||||
needs: ['admin-customize-css-html'],
|
||||
|
||||
title: "hi",
|
||||
|
||||
ready: function() {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(this.get('customizationFile'));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!parsed["site_customization"];
|
||||
}.property('customizationFile'),
|
||||
|
||||
actions: {
|
||||
createCustomization: function() {
|
||||
const self = this;
|
||||
const object = JSON.parse(this.get('customizationFile')).site_customization;
|
||||
|
||||
// Slight fixup before creating object
|
||||
object.enabled = false;
|
||||
delete object.id;
|
||||
delete object.key;
|
||||
|
||||
const customization = Discourse.SiteCustomization.create(object);
|
||||
|
||||
this.set('loading', true);
|
||||
customization.save().then(function(customization) {
|
||||
self.send('closeModal');
|
||||
self.set('loading', false);
|
||||
|
||||
const parentController = self.get('controllers.admin-customize-css-html');
|
||||
parentController.pushObject(customization);
|
||||
parentController.set('selectedItem', customization);
|
||||
}).catch(function(xhr) {
|
||||
self.set('loading', false);
|
||||
if (xhr.responseJSON) {
|
||||
bootbox.alert(xhr.responseJSON.errors.join("<br>"));
|
||||
} else {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -89,6 +89,10 @@ export default Ember.DefaultResolver.extend({
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveModel(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveView(parsedName) {
|
||||
return this.findLoadingView(parsedName) || this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
@ -4,21 +4,13 @@ var safe = Handlebars.SafeString;
|
||||
var registerUnbound = require('discourse/helpers/register-unbound', null, null, true).default;
|
||||
var avatarTemplate = require('discourse/lib/avatar-template', null, null, true).default;
|
||||
|
||||
/**
|
||||
Bound avatar helper.
|
||||
|
||||
@method bound-avatar
|
||||
@for Handlebars
|
||||
**/
|
||||
Em.Handlebars.helper('bound-avatar', function(user, size, uploadId) {
|
||||
if (Em.isEmpty(user)) {
|
||||
return new safe("<div class='avatar-placeholder'></div>");
|
||||
}
|
||||
|
||||
var username = Em.get(user, 'username');
|
||||
|
||||
if (arguments.length < 4) { uploadId = Em.get(user, 'uploaded_avatar_id'); }
|
||||
|
||||
var avatar = Em.get(user, 'avatar_template') || avatarTemplate(username, uploadId);
|
||||
|
||||
return new safe(Discourse.Utilities.avatarImg({ size: size, avatarTemplate: avatar }));
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||
|
||||
const TITLE_SUBS = {
|
||||
all: 'all_time',
|
||||
yearly: 'this_year',
|
||||
@ -7,7 +5,26 @@ const TITLE_SUBS = {
|
||||
daily: 'today',
|
||||
};
|
||||
|
||||
export default Ember.Handlebars.makeBoundHelper(function (period) {
|
||||
export default Ember.Handlebars.makeBoundHelper(function (period, options) {
|
||||
const title = I18n.t('filters.top.' + (TITLE_SUBS[period] || 'this_week'));
|
||||
return new Handlebars.SafeString(iconHTML('calendar-o') + " " + title);
|
||||
if (options.hash.showDateRange) {
|
||||
var dateString = "";
|
||||
switch(period) {
|
||||
case 'yearly':
|
||||
dateString = moment().subtract(1, 'year').format(I18n.t('dates.full_with_year_no_time'));
|
||||
break;
|
||||
case 'weekly':
|
||||
dateString = moment().subtract(1, 'week').format(I18n.t('dates.full_no_year_no_time')) + " - " + moment().format(I18n.t('dates.full_no_year_no_time'));
|
||||
break;
|
||||
case 'monthly':
|
||||
dateString = moment().subtract(1, 'month').format(I18n.t('dates.full_no_year_no_time')) + " - " + moment().format(I18n.t('dates.full_no_year_no_time'));
|
||||
break;
|
||||
case 'daily':
|
||||
dateString = moment().format(I18n.t('dates.full_no_year_no_time'));
|
||||
break;
|
||||
}
|
||||
return new Handlebars.SafeString(title + " <span class='top-date-string'>" + dateString + "</span>");
|
||||
} else {
|
||||
return new Handlebars.SafeString(title);
|
||||
}
|
||||
});
|
||||
|
||||
@ -2,5 +2,6 @@ import registerUnbound from 'discourse/helpers/register-unbound';
|
||||
|
||||
registerUnbound('topic-link', function(topic) {
|
||||
var title = topic.get('fancyTitle');
|
||||
return new Handlebars.SafeString("<a href='" + topic.get('lastUnreadUrl') + "' class='title'>" + title + "</a>");
|
||||
var url = topic.linked_post_number ? topic.urlForPostNumber(topic.linked_post_number) : topic.get('lastUnreadUrl');
|
||||
return new Handlebars.SafeString("<a href='" + url + "' class='title'>" + title + "</a>");
|
||||
});
|
||||
|
||||
@ -64,6 +64,10 @@ export default {
|
||||
$(this).data('orig', this.href);
|
||||
}
|
||||
const orig = $(this).data('orig');
|
||||
if (!me.hash) {
|
||||
window.__uniq = window.__uniq || 1;
|
||||
me.hash = window.__uniq++;
|
||||
}
|
||||
this.href = orig + (orig.indexOf('?') >= 0 ? "&hash=" : "?hash=") + me.hash;
|
||||
}
|
||||
});
|
||||
|
||||
@ -28,7 +28,7 @@ function extractError(error) {
|
||||
|
||||
if (parsedJSON) {
|
||||
if (parsedJSON.errors && parsedJSON.errors.length > 0) {
|
||||
parsedError = parsedJSON.errors[0];
|
||||
parsedError = parsedJSON.errors.join("<br>");
|
||||
} else if (parsedJSON.error) {
|
||||
parsedError = parsedJSON.error;
|
||||
} else if (parsedJSON.failed) {
|
||||
@ -36,6 +36,12 @@ function extractError(error) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!parsedError) {
|
||||
if (error.status && error.status >= 400) {
|
||||
parsedError = error.status + " " + error.statusText;
|
||||
}
|
||||
}
|
||||
|
||||
return parsedError || I18n.t('generic_error');
|
||||
}
|
||||
|
||||
|
||||
@ -86,6 +86,7 @@ var translations = {
|
||||
':-D' : 'smiley',
|
||||
':|' : 'expressionless',
|
||||
':-|' : 'expressionless',
|
||||
':/' : 'confused',
|
||||
";P" : 'stuck_out_tongue_winking_eye',
|
||||
";-P" : 'stuck_out_tongue_winking_eye',
|
||||
":$" : 'blush',
|
||||
|
||||
@ -188,7 +188,7 @@ relativeAgeMediumSpan = function(distance, leaveAgo) {
|
||||
};
|
||||
|
||||
switch(true){
|
||||
case(distanceInMinutes >= 1 && distanceInMinutes <= 56):
|
||||
case(distanceInMinutes >= 1 && distanceInMinutes <= 55):
|
||||
formatted = t("x_minutes", {count: distanceInMinutes});
|
||||
break;
|
||||
case(distanceInMinutes >= 56 && distanceInMinutes <= 89):
|
||||
@ -258,14 +258,18 @@ relativeAge = function(date, options) {
|
||||
};
|
||||
|
||||
var number = function(val) {
|
||||
var formattedNumber;
|
||||
|
||||
val = parseInt(val, 10);
|
||||
if (isNaN(val)) val = 0;
|
||||
|
||||
if (val > 999999) {
|
||||
return (val / 1000000).toFixed(1) + "M";
|
||||
formattedNumber = I18n.toNumber(val / 1000000, {precision: 1});
|
||||
return I18n.t("number.short.millions", {number: formattedNumber});
|
||||
}
|
||||
if (val > 999) {
|
||||
return (val / 1000).toFixed(1) + "K";
|
||||
formattedNumber = I18n.toNumber(val / 1000, {precision: 1});
|
||||
return I18n.t("number.short.thousands", {number: formattedNumber});
|
||||
}
|
||||
return val.toString();
|
||||
};
|
||||
|
||||
@ -59,6 +59,7 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
|
||||
bindEvents: function(keyTrapper, container) {
|
||||
this.keyTrapper = keyTrapper;
|
||||
this.container = container;
|
||||
this._stopCallback();
|
||||
|
||||
_.each(PATH_BINDINGS, this._bindToPath, this);
|
||||
_.each(CLICK_BINDINGS, this._bindToClick, this);
|
||||
@ -128,6 +129,11 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
|
||||
},
|
||||
|
||||
showBuiltinSearch: function() {
|
||||
if ($('#search-dropdown').is(':visible')) {
|
||||
this._toggleSearch(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
var currentPath = this.container.lookup('controller:application').get('currentPath'),
|
||||
blacklist = [ /^discovery\.categories/ ],
|
||||
whitelist = [ /^topic\./ ],
|
||||
@ -137,10 +143,14 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
|
||||
// If we're viewing a topic, only intercept search if there are cloaked posts
|
||||
if (showSearch && currentPath.match(/^topic\./)) {
|
||||
showSearch = $('.cooked').length < this.container.lookup('controller:topic').get('postStream.stream.length');
|
||||
|
||||
}
|
||||
|
||||
return showSearch ? this.showSearch(true) : true;
|
||||
if (showSearch) {
|
||||
this._toggleSearch(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
createTopic: function() {
|
||||
@ -155,11 +165,8 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
|
||||
Discourse.__container__.lookup('controller:topic-progress').send('toggleExpansion', {highlight: true});
|
||||
},
|
||||
|
||||
showSearch: function(selectContext) {
|
||||
$('#search-button').click();
|
||||
if(selectContext) {
|
||||
Discourse.__container__.lookup('controller:search').set('searchContextEnabled', true);
|
||||
}
|
||||
showSearch: function() {
|
||||
this._toggleSearch(false);
|
||||
return false;
|
||||
},
|
||||
|
||||
@ -304,22 +311,32 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
|
||||
tabLoc.focus();
|
||||
}
|
||||
|
||||
var rgx = new RegExp("post-cloak-(\\d+)").exec($article.parent()[0].id);
|
||||
if (rgx === null || typeof rgx[1] === 'undefined') {
|
||||
this._scrollList($article, direction);
|
||||
} else {
|
||||
Discourse.URL.jumpToPost(rgx[1]);
|
||||
}
|
||||
this._scrollList($article, direction);
|
||||
}
|
||||
},
|
||||
|
||||
_scrollList: function($article) {
|
||||
// Try to keep the article on screen
|
||||
var scrollPos = $article.position().top - ($(window).height() * 0.5);
|
||||
var pos = $article.offset();
|
||||
var height = $article.height();
|
||||
var scrollTop = $(window).scrollTop();
|
||||
var windowHeight = $(window).height();
|
||||
|
||||
// skip if completely on screen
|
||||
if (pos.top > scrollTop && (pos.top + height) < (scrollTop + windowHeight)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var scrollPos = (pos.top + (height/2)) - (windowHeight * 0.5);
|
||||
if (scrollPos < 0) { scrollPos = 0; }
|
||||
$('html, body').scrollTop(scrollPos);
|
||||
|
||||
if (this._scrollAnimation) {
|
||||
this._scrollAnimation.stop();
|
||||
}
|
||||
this._scrollAnimation = $("html, body").animate({ scrollTop: scrollPos + "px"}, 100);
|
||||
},
|
||||
|
||||
|
||||
_findArticles: function() {
|
||||
var $topicList = $('.topic-list'),
|
||||
$topicArea = $('.posts-wrapper');
|
||||
@ -340,5 +357,24 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
|
||||
if(index >= 0 && index < $sections.length){
|
||||
$sections.eq(index).find('a').click();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_stopCallback: function() {
|
||||
var oldStopCallback = this.keyTrapper.stopCallback;
|
||||
|
||||
this.keyTrapper.stopCallback = function(e, element, combo) {
|
||||
if ((combo === 'ctrl+f' || combo === 'command+f') && element.id === 'search-term') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return oldStopCallback(e, element, combo);
|
||||
};
|
||||
},
|
||||
|
||||
_toggleSearch: function(selectContext) {
|
||||
$('#search-button').click();
|
||||
if (selectContext) {
|
||||
Discourse.__container__.lookup('controller:search').set('searchContextEnabled', true);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import decimalAdjust from "discourse/plugins/poll/lib/decimal-adjust";
|
||||
import decimalAdjust from "discourse/lib/decimal-adjust";
|
||||
|
||||
export default function(value, exp) {
|
||||
return decimalAdjust("round", value, exp);
|
||||
@ -4,7 +4,7 @@ function searchForTerm(term, opts) {
|
||||
if (!opts) opts = {};
|
||||
|
||||
// Only include the data we have
|
||||
var data = { term: term, include_blurbs: 'true' };
|
||||
const data = { term: term, include_blurbs: 'true' };
|
||||
if (opts.typeFilter) data.type_filter = opts.typeFilter;
|
||||
if (opts.searchForId) data.search_for_id = true;
|
||||
|
||||
@ -22,7 +22,7 @@ function searchForTerm(term, opts) {
|
||||
if (!results.posts) { results.posts = []; }
|
||||
if (!results.categories) { results.categories = []; }
|
||||
|
||||
var topicMap = {};
|
||||
const topicMap = {};
|
||||
results.topics = results.topics.map(function(topic){
|
||||
topic = Topic.create(topic);
|
||||
topicMap[topic.id] = topic;
|
||||
@ -44,23 +44,23 @@ function searchForTerm(term, opts) {
|
||||
return Discourse.Category.list().findProperty('id', category.id);
|
||||
}).compact();
|
||||
|
||||
var r = results.grouped_search_result;
|
||||
const r = results.grouped_search_result;
|
||||
results.resultTypes = [];
|
||||
|
||||
// TODO: consider refactoring front end to take a better structure
|
||||
[['topic','posts'],['user','users'],['category','categories']].forEach(function(pair){
|
||||
var type = pair[0], name = pair[1];
|
||||
if(results[name].length > 0) {
|
||||
const type = pair[0], name = pair[1];
|
||||
if (results[name].length > 0) {
|
||||
results.resultTypes.push({
|
||||
results: results[name],
|
||||
displayType: (opts.searchContext && opts.searchContext.type === 'topic' && type === 'topic') ? 'post' : type,
|
||||
type: type,
|
||||
componentName: "search-result-" + ((opts.searchContext && opts.searchContext.type === 'topic' && type === 'topic') ? 'post' : type),
|
||||
type,
|
||||
more: r['more_' + name]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var noResults = !!(results.topics.length === 0 &&
|
||||
const noResults = !!(results.topics.length === 0 &&
|
||||
results.posts.length === 0 &&
|
||||
results.users.length === 0 &&
|
||||
results.categories.length === 0);
|
||||
|
||||
@ -109,7 +109,6 @@ Discourse.URL = Ember.Object.createWithMixins({
|
||||
@param {String} path The path we are routing to.
|
||||
**/
|
||||
routeTo: function(path) {
|
||||
|
||||
if (Em.isEmpty(path)) { return; }
|
||||
|
||||
if (Discourse.get('requiresRefresh')) {
|
||||
|
||||
@ -9,6 +9,24 @@
|
||||
|
||||
Discourse.NavItem = Discourse.Model.extend({
|
||||
|
||||
displayName: function() {
|
||||
var categoryName = this.get('categoryName'),
|
||||
name = this.get('name'),
|
||||
count = this.get('count') || 0;
|
||||
|
||||
if (name === 'latest' && !Discourse.Mobile.mobileView) {
|
||||
count = 0;
|
||||
}
|
||||
|
||||
var extra = { count: count };
|
||||
|
||||
if (categoryName) {
|
||||
name = 'category';
|
||||
extra.categoryName = Discourse.Formatter.toTitleCase(categoryName);
|
||||
}
|
||||
return I18n.t("filters." + name.replace("/", ".") + ".title", extra);
|
||||
}.property('categoryName,name,count'),
|
||||
|
||||
topicTrackingState: function() {
|
||||
return Discourse.TopicTrackingState.current();
|
||||
}.property(),
|
||||
|
||||
@ -6,16 +6,20 @@ function calcDayDiff(p1, p2) {
|
||||
const date = p1.get('created_at');
|
||||
if (date) {
|
||||
if (p2) {
|
||||
const lastDate = p2.get('created_at');
|
||||
if (lastDate) {
|
||||
const delta = new Date(date).getTime() - new Date(lastDate).getTime();
|
||||
const days = Math.round(delta / (1000 * 60 * 60 * 24));
|
||||
const numDiff = p1.get('post_number') - p2.get('post_number');
|
||||
if (numDiff === 1) {
|
||||
const lastDate = p2.get('created_at');
|
||||
if (lastDate) {
|
||||
const delta = new Date(date).getTime() - new Date(lastDate).getTime();
|
||||
const days = Math.round(delta / (1000 * 60 * 60 * 24));
|
||||
|
||||
p1.set('daysSincePrevious', days);
|
||||
p1.set('daysSincePrevious', days);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PostStream = RestModel.extend({
|
||||
loading: Em.computed.or('loadingAbove', 'loadingBelow', 'loadingFilter', 'stagingPost'),
|
||||
notLoading: Em.computed.not('loading'),
|
||||
@ -137,7 +141,13 @@ const PostStream = RestModel.extend({
|
||||
toggleSummary() {
|
||||
this.get('userFilters').clear();
|
||||
this.toggleProperty('summary');
|
||||
return this.refresh();
|
||||
|
||||
const self = this;
|
||||
return this.refresh().then(function() {
|
||||
if (self.get('summary')) {
|
||||
self.jumpToSecondVisible();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toggleDeleted() {
|
||||
@ -145,17 +155,33 @@ const PostStream = RestModel.extend({
|
||||
return this.refresh();
|
||||
},
|
||||
|
||||
jumpToSecondVisible() {
|
||||
const posts = this.get('posts');
|
||||
if (posts.length > 1) {
|
||||
const secondPostNum = posts[1].get('post_number');
|
||||
Discourse.URL.jumpToPost(secondPostNum);
|
||||
}
|
||||
},
|
||||
|
||||
// Filter the stream to a particular user.
|
||||
toggleParticipant(username) {
|
||||
const userFilters = this.get('userFilters');
|
||||
this.set('summary', false);
|
||||
this.set('show_deleted', true);
|
||||
|
||||
let jump = false;
|
||||
if (userFilters.contains(username)) {
|
||||
userFilters.removeObject(username);
|
||||
} else {
|
||||
userFilters.addObject(username);
|
||||
jump = true;
|
||||
}
|
||||
return this.refresh();
|
||||
const self = this;
|
||||
return this.refresh().then(function() {
|
||||
if (jump) {
|
||||
self.jumpToSecondVisible();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -50,6 +50,12 @@ const Post = RestModel.extend({
|
||||
return Discourse.Utilities.postUrl(this.get('topic.slug') || this.get('topic_slug'), this.get('topic_id'), this.get('post_number'));
|
||||
}.property('post_number', 'topic_id', 'topic.slug'),
|
||||
|
||||
// Don't drop the /1
|
||||
urlWithNumber: function() {
|
||||
const url = this.get('url');
|
||||
return (this.get('post_number') === 1) ? url + "/1" : url;
|
||||
}.property('post_number', 'url'),
|
||||
|
||||
usernameUrl: Discourse.computed.url('username', '/users/%@'),
|
||||
|
||||
showUserReplyTab: function() {
|
||||
|
||||
@ -37,8 +37,13 @@ function findAndRemoveMap(type, id) {
|
||||
flushMap();
|
||||
|
||||
export default Ember.Object.extend({
|
||||
_plurals: {},
|
||||
pluralize(thing) {
|
||||
return thing + "s";
|
||||
return this._plurals[thing] || thing + "s";
|
||||
},
|
||||
|
||||
addPluralization(thing, plural) {
|
||||
this._plurals[thing] = plural;
|
||||
},
|
||||
|
||||
findAll(type) {
|
||||
@ -143,10 +148,13 @@ export default Ember.Object.extend({
|
||||
return this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
||||
},
|
||||
|
||||
_lookupSubType(subType, id, root) {
|
||||
_lookupSubType(subType, type, id, root) {
|
||||
|
||||
// cheat: we know we already have categories in memory
|
||||
if (subType === 'category') {
|
||||
// TODO: topics do their own resolving of `category_id`
|
||||
// to category. That should either respect this or be
|
||||
// removed.
|
||||
if (subType === 'category' && type !== 'topic') {
|
||||
return Discourse.Category.findById(id);
|
||||
}
|
||||
|
||||
@ -172,13 +180,13 @@ export default Ember.Object.extend({
|
||||
}
|
||||
},
|
||||
|
||||
_hydrateEmbedded(obj, root) {
|
||||
_hydrateEmbedded(type, obj, root) {
|
||||
const self = this;
|
||||
Object.keys(obj).forEach(function(k) {
|
||||
const m = /(.+)\_id$/.exec(k);
|
||||
if (m) {
|
||||
const subType = m[1];
|
||||
const hydrated = self._lookupSubType(subType, obj[k], root);
|
||||
const hydrated = self._lookupSubType(subType, type, obj[k], root);
|
||||
if (hydrated) {
|
||||
obj[subType] = hydrated;
|
||||
delete obj[k];
|
||||
@ -196,7 +204,7 @@ export default Ember.Object.extend({
|
||||
// Experimental: If serialized with a certain option we'll wire up embedded objects
|
||||
// automatically.
|
||||
if (root.__rest_serializer === "1") {
|
||||
this._hydrateEmbedded(obj, root);
|
||||
this._hydrateEmbedded(type, obj, root);
|
||||
}
|
||||
|
||||
const existing = fromMap(type, obj.id);
|
||||
|
||||
@ -40,8 +40,8 @@ const TopicList = RestModel.extend({
|
||||
},
|
||||
|
||||
refreshSort: function(order, ascending) {
|
||||
const self = this,
|
||||
params = this.get('params') || {};
|
||||
const self = this;
|
||||
var params = this.get('params') || {};
|
||||
|
||||
params.order = order || params.order;
|
||||
|
||||
@ -51,7 +51,14 @@ const TopicList = RestModel.extend({
|
||||
params.ascending = ascending;
|
||||
}
|
||||
|
||||
if (params.q) {
|
||||
// search is unique, nothing else allowed with it
|
||||
params = {q: params.q};
|
||||
}
|
||||
|
||||
this.set('loaded', false);
|
||||
this.set('params', params);
|
||||
|
||||
const store = this.store;
|
||||
store.findFiltered('topicList', {filter: this.get('filter'), params}).then(function(tl) {
|
||||
const newTopics = tl.get('topics'),
|
||||
|
||||
@ -266,6 +266,12 @@ const TopicTrackingState = Discourse.Model.extend({
|
||||
},
|
||||
|
||||
lookupCount(name, category){
|
||||
|
||||
if (name === "latest") {
|
||||
return this.lookupCount("new", category) +
|
||||
this.lookupCount("unread", category);
|
||||
}
|
||||
|
||||
let categoryName = category ? Em.get(category, "name") : null;
|
||||
if(name === "new") {
|
||||
return this.countNew(categoryName);
|
||||
@ -278,6 +284,7 @@ const TopicTrackingState = Discourse.Model.extend({
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
loadStates(data) {
|
||||
// not exposed
|
||||
const states = this.states;
|
||||
|
||||
@ -363,8 +363,7 @@ const Topic = RestModel.extend({
|
||||
);
|
||||
},
|
||||
|
||||
excerptNotEmpty: Em.computed.notEmpty('excerpt'),
|
||||
hasExcerpt: Em.computed.and('pinned', 'excerptNotEmpty'),
|
||||
hasExcerpt: Em.computed.notEmpty('excerpt'),
|
||||
|
||||
excerptTruncated: function() {
|
||||
const e = this.get('excerpt');
|
||||
|
||||
@ -16,6 +16,7 @@ function findTopicList(store, filter, filterParams, extras) {
|
||||
|
||||
extras = extras || {};
|
||||
return new Ember.RSVP.Promise(function(resolve) {
|
||||
|
||||
const session = Discourse.Session.current();
|
||||
|
||||
if (extras.cached) {
|
||||
@ -80,7 +81,6 @@ export default function(filter, extras) {
|
||||
},
|
||||
|
||||
model(data, transition) {
|
||||
|
||||
// attempt to stop early cause we need this to be called before .sync
|
||||
Discourse.ScreenTrack.current().stop();
|
||||
|
||||
@ -115,8 +115,12 @@ export default function(filter, extras) {
|
||||
|
||||
const params = model.get('params');
|
||||
if (params && Object.keys(params).length) {
|
||||
topicOpts.order = params.order;
|
||||
topicOpts.ascending = params.ascending;
|
||||
if (params.order !== undefined) {
|
||||
topicOpts.order = params.order;
|
||||
}
|
||||
if (params.ascending !== undefined) {
|
||||
topicOpts.ascending = params.ascending;
|
||||
}
|
||||
}
|
||||
this.controllerFor('discovery/topics').setProperties(topicOpts);
|
||||
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
var INDEX_STREAM_ROUTES = ["user.deletedPosts", "user.flaggedPosts", "userActivity.index"];
|
||||
const INDEX_STREAM_ROUTES = ["user.deletedPosts", "user.flaggedPosts", "userActivity.index"];
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
|
||||
titleToken: function() {
|
||||
var model = this.modelFor('user');
|
||||
var username = model.get('username');
|
||||
titleToken() {
|
||||
const model = this.modelFor('user');
|
||||
const username = model.get('username');
|
||||
if (username) {
|
||||
return [I18n.t("user.profile"), username];
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
logout: function() {
|
||||
logout() {
|
||||
Discourse.logout();
|
||||
},
|
||||
|
||||
composePrivateMessage: function(user, post) {
|
||||
var recipient = user ? user.get('username') : '',
|
||||
composePrivateMessage(user, post) {
|
||||
const recipient = user ? user.get('username') : '',
|
||||
reply = post ? window.location.protocol + "//" + window.location.host + post.get("url") : null;
|
||||
|
||||
return this.controllerFor('composer').open({
|
||||
@ -28,18 +28,18 @@ export default Discourse.Route.extend({
|
||||
});
|
||||
},
|
||||
|
||||
willTransition: function(transition) {
|
||||
willTransition(transition) {
|
||||
// will reset the indexStream when transitioning to routes that aren't "indexStream"
|
||||
// otherwise the "header" will jump
|
||||
var isIndexStream = ~INDEX_STREAM_ROUTES.indexOf(transition.targetName);
|
||||
const isIndexStream = ~INDEX_STREAM_ROUTES.indexOf(transition.targetName);
|
||||
this.controllerFor('user').set('indexStream', isIndexStream);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
model: function(params) {
|
||||
model(params) {
|
||||
// If we're viewing the currently logged in user, return that object instead
|
||||
var currentUser = Discourse.User.current();
|
||||
const currentUser = this.currentUser;
|
||||
if (currentUser && (params.username.toLowerCase() === currentUser.get('username_lower'))) {
|
||||
return currentUser;
|
||||
}
|
||||
@ -47,34 +47,38 @@ export default Discourse.Route.extend({
|
||||
return Discourse.User.create({username: params.username});
|
||||
},
|
||||
|
||||
afterModel: function() {
|
||||
var user = this.modelFor('user');
|
||||
afterModel() {
|
||||
const user = this.modelFor('user');
|
||||
const self = this;
|
||||
|
||||
return user.findDetails().then(function() {
|
||||
return user.findStaffInfo();
|
||||
}).catch(function() {
|
||||
return self.replaceWith('/404');
|
||||
});
|
||||
},
|
||||
|
||||
serialize: function(model) {
|
||||
serialize(model) {
|
||||
if (!model) return {};
|
||||
return { username: (Em.get(model, 'username') || '').toLowerCase() };
|
||||
},
|
||||
|
||||
setupController: function(controller, user) {
|
||||
setupController(controller, user) {
|
||||
controller.set('model', user);
|
||||
|
||||
// Add a search context
|
||||
this.controllerFor('search').set('searchContext', user.get('searchContext'));
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
activate() {
|
||||
this._super();
|
||||
var user = this.modelFor('user');
|
||||
const user = this.modelFor('user');
|
||||
this.messageBus.subscribe("/users/" + user.get('username_lower'), function(data) {
|
||||
user.loadUserAction(data);
|
||||
});
|
||||
},
|
||||
|
||||
deactivate: function() {
|
||||
deactivate() {
|
||||
this._super();
|
||||
this.messageBus.unsubscribe("/users/" + this.modelFor('user').get('username_lower'));
|
||||
|
||||
|
||||
@ -3,7 +3,10 @@
|
||||
{{topic-list
|
||||
showParticipants=showParticipants
|
||||
hideCategory=hideCategory
|
||||
topics=topics}}
|
||||
topics=topics
|
||||
expandExcerpts=expandExcerpts
|
||||
searchTerm=searchTerm
|
||||
}}
|
||||
{{else}}
|
||||
<div class='alert alert-info'>
|
||||
{{i18n 'choose_topic.none_found'}}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
<div class="jsfu-shade-container">
|
||||
<div class="jsfu-file">
|
||||
<input id="js-file-input" type="file" style="display:none;" accept={{accept}}>
|
||||
{{d-button class="fileSelect" action="selectFile" class="" icon="upload" label="upload_selector.select_file"}}
|
||||
{{conditional-loading-spinner condition=loading size="small"}}
|
||||
</div>
|
||||
<div class="jsfu-separator">{{i18n "alternation"}}</div>
|
||||
<div class="jsfu-paste">
|
||||
{{textarea value=value}}
|
||||
</div>
|
||||
<div class="jsfu-shade {{if hover '' 'hidden'}}"><span class="text">{{fa-icon "upload"}}</span></div>
|
||||
</div>
|
||||
@ -0,0 +1,4 @@
|
||||
{{#each navItem in navItems}}
|
||||
{{navigation-item content=navItem filterMode=filterMode}}
|
||||
{{/each}}
|
||||
{{custom-html "extraNavItem"}}
|
||||
@ -1,4 +1,4 @@
|
||||
<h2>{{period-title period}}</h2>
|
||||
<h2>{{period-title period showDateRange=true}}</h2>
|
||||
<button>{{fa-icon "caret-down"}}</button>
|
||||
|
||||
<div id='period-popup' {{bind-attr class="showPeriods::hidden :period-popup"}}>
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
{{#each results as |result|}}
|
||||
<li>
|
||||
<a href='{{unbound result.url}}'>
|
||||
{{category-badge result}}
|
||||
</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
@ -0,0 +1,12 @@
|
||||
{{#each results as |result|}}
|
||||
<a class='search-link' href='{{unbound result.urlWithNumber}}'>
|
||||
<span class='topic'>
|
||||
{{i18n 'search.post_format' post_number=result.post_number username=result.username}}
|
||||
</span>
|
||||
{{#unless site.mobileView}}
|
||||
<span class='blurb'>
|
||||
{{{unbound result.blurb}}}
|
||||
</span>
|
||||
{{/unless}}
|
||||
</a>
|
||||
{{/each}}
|
||||
@ -0,0 +1,14 @@
|
||||
{{#each results as |result|}}
|
||||
<li>
|
||||
<a class='search-link' href='{{unbound result.url}}'>
|
||||
<span class='topic'>
|
||||
{{topic-status topic=result.topic disableActions=true}}<span class='topic-title'>{{unbound result.topic.title}}</span>{{category-badge result.topic.category}}
|
||||
</span>
|
||||
{{#unless site.mobileView}}
|
||||
<span class='blurb'>
|
||||
{{format-age result.created_at}} - {{{unbound result.blurb}}}
|
||||
</span>
|
||||
{{/unless}}
|
||||
</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
@ -0,0 +1,8 @@
|
||||
{{#each results as |result|}}
|
||||
<li>
|
||||
<a href='{{unbound result.path}}'>
|
||||
{{avatar result imageSize="small"}}
|
||||
{{unbound result.username}}
|
||||
</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
@ -2,11 +2,5 @@
|
||||
<h3>{{i18n 'composer.similar_topics'}}</h3>
|
||||
|
||||
<ul class='topics'>
|
||||
{{#each similarTopics as |t|}}
|
||||
<li>
|
||||
{{topic-status topic=t}}
|
||||
{{topic-link t}}
|
||||
{{category-link t.category}}
|
||||
</li>
|
||||
{{/each}}
|
||||
{{search-result-topic results=similarTopics}}
|
||||
</ul>
|
||||
|
||||
@ -46,6 +46,8 @@
|
||||
selected=selected
|
||||
expandGloballyPinned=expandGloballyPinned
|
||||
expandAllPinned=expandAllPinned
|
||||
expandExcerpts=isSearch
|
||||
searchTerm=searchTerm
|
||||
topics=model.topics}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
<li>
|
||||
<a {{action "toggleDrop"}}>
|
||||
{{selectedNavItem.displayName}}
|
||||
<i class='fa fa-caret-down'></i>
|
||||
</a>
|
||||
</li>
|
||||
{{#if expanded}}
|
||||
<ul class='drop'>
|
||||
{{#each navItem in navItems}}
|
||||
{{navigation-item content=navItem filterMode=filterMode}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
@ -21,17 +21,19 @@
|
||||
<td><label>{{i18n 'user.email.instructions'}}</label></td>
|
||||
</tr>
|
||||
|
||||
<tr class="input">
|
||||
<td class="label"><label for='new-account-username'>{{i18n 'user.username.title'}}</label></td>
|
||||
<td>
|
||||
{{input value=accountUsername id="new-account-username" name="username" maxlength=maxUsernameLength}}
|
||||
{{input-tip validation=usernameValidation id="username-validation"}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="instructions">
|
||||
<td></td>
|
||||
<td><label>{{i18n 'user.username.instructions'}}</label></td>
|
||||
</tr>
|
||||
{{#if usernameRequired}}
|
||||
<tr class="input">
|
||||
<td class="label"><label for='new-account-username'>{{i18n 'user.username.title'}}</label></td>
|
||||
<td>
|
||||
{{input value=accountUsername id="new-account-username" name="username" maxlength=maxUsernameLength}}
|
||||
{{input-tip validation=usernameValidation id="username-validation"}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="instructions">
|
||||
<td></td>
|
||||
<td><label>{{i18n 'user.username.instructions'}}</label></td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
||||
<tr class="input">
|
||||
<td style="width:80px" class="label"><label for='new-account-name'>{{i18n 'user.name.title'}}</label></td>
|
||||
@ -46,20 +48,20 @@
|
||||
</tr>
|
||||
|
||||
{{#if passwordRequired}}
|
||||
<tr class="input">
|
||||
<td class="label"><label for='new-account-password'>{{i18n 'user.password.title'}}</label></td>
|
||||
<tr class="input">
|
||||
<td class="label"><label for='new-account-password'>{{i18n 'user.password.title'}}</label></td>
|
||||
<td>
|
||||
{{password-field value=accountPassword type="password" id="new-account-password" capsLockOn=capsLockOn}}
|
||||
{{input-tip validation=passwordValidation}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="instructions">
|
||||
<td></td>
|
||||
<td>
|
||||
{{password-field value=accountPassword type="password" id="new-account-password" capsLockOn=capsLockOn}}
|
||||
{{input-tip validation=passwordValidation}}
|
||||
<label>{{passwordInstructions}}</label>
|
||||
<div {{bind-attr class=":caps-lock-warning capsLockOn::invisible"}}><i class="fa fa-exclamation-triangle"></i> {{i18n 'login.caps_lock_warning'}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="instructions">
|
||||
<td></td>
|
||||
<td>
|
||||
<label>{{passwordInstructions}}</label>
|
||||
<div {{bind-attr class=":caps-lock-warning capsLockOn::invisible"}}><i class="fa fa-exclamation-triangle"></i> {{i18n 'login.caps_lock_warning'}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
||||
<tr class="password-confirmation">
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
<form {{action "dummy" on="submit"}}>
|
||||
<div class='modal-body'>
|
||||
{{json-file-uploader value=customizationFile extension=".dcstyle.json"}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{{d-button class='btn-primary' action='createCustomization' type='submit' disabled=notReady icon="plus" label='admin.customize.import'}}
|
||||
</div>
|
||||
</form>
|
||||
@ -1,11 +1,6 @@
|
||||
{{bread-crumbs categories=categories}}
|
||||
|
||||
<ul class="nav nav-pills" id='navigation-bar'>
|
||||
{{#each navItem in navItems}}
|
||||
{{navigation-item content=navItem filterMode=filterMode}}
|
||||
{{/each}}
|
||||
{{custom-html "extraNavItem"}}
|
||||
</ul>
|
||||
{{navigation-bar navItems=navItems filterMode=filterMode}}
|
||||
|
||||
{{#if canCreateCategory}}
|
||||
<button class='btn btn-default' {{action "createCategory"}}><i class='fa fa-plus'></i>{{i18n 'category.create'}}</button>
|
||||
|
||||
@ -3,12 +3,8 @@
|
||||
noSubcategories=noSubcategories
|
||||
hideSubcategories=showingSubcategoryList}}
|
||||
|
||||
<ul class="nav nav-pills" id='navigation-bar'>
|
||||
{{#each navItem in navItems}}
|
||||
{{navigation-item content=navItem filterMode=filterMode}}
|
||||
{{/each}}
|
||||
{{custom-html "extraNavItem"}}
|
||||
</ul>
|
||||
|
||||
{{navigation-bar navItems=navItems filterMode=filterMode}}
|
||||
|
||||
{{#if currentUser}}
|
||||
{{category-notifications-button category=category}}
|
||||
|
||||
@ -6,12 +6,7 @@
|
||||
{{else}}
|
||||
{{bread-crumbs categories=categories}}
|
||||
|
||||
<ul class="nav nav-pills" id='navigation-bar'>
|
||||
{{#each navItem in navItems}}
|
||||
{{navigation-item content=navItem filterMode=filterMode}}
|
||||
{{/each}}
|
||||
{{custom-html "extraNavItem"}}
|
||||
</ul>
|
||||
{{navigation-bar navItems=navItems filterMode=filterMode}}
|
||||
|
||||
{{#if canCreateTopic}}
|
||||
<button id="create-topic" class='btn btn-default' {{action "createTopic"}}><i class='fa fa-plus'></i>{{i18n 'topic.create'}}</button>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
{{view "search-text-field" value=term searchContextEnabled=searchContextEnabled searchContext=searchContext id="search-term"}}
|
||||
{{plugin-outlet "above-search"}}
|
||||
{{search-text-field value=term searchContextEnabled=searchContextEnabled id="search-term"}}
|
||||
|
||||
<div class="search-context">
|
||||
{{#if searchContext}}
|
||||
<label>
|
||||
@ -16,10 +18,10 @@
|
||||
{{i18n "search.no_results"}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{#each resultType in content.resultTypes}}
|
||||
{{#each content.resultTypes as |resultType|}}
|
||||
<ul>
|
||||
<li class="heading row">{{resultType.name}}</li>
|
||||
{{view "search-results-type" type=resultType.type displayType=resultType.displayType content=resultType.results}}
|
||||
{{component resultType.componentName results=resultType.results term=term}}
|
||||
</ul>
|
||||
<div class="no-results">
|
||||
{{#if resultType.more}}
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
<a href='{{unbound url}}'>
|
||||
{{category-badge this}}
|
||||
</a>
|
||||
@ -1,10 +0,0 @@
|
||||
<a class='search-link' href='{{unbound url}}'>
|
||||
<span class='topic'>
|
||||
{{i18n 'search.post_format' post_number=post_number username=username}}
|
||||
</span>
|
||||
{{#unless controller.site.mobileView}}
|
||||
<span class='blurb'>
|
||||
{{{unbound blurb}}}
|
||||
</span>
|
||||
{{/unless}}
|
||||
</a>
|
||||
@ -1,10 +0,0 @@
|
||||
<a class='search-link' href='{{unbound url}}'>
|
||||
<span class='topic'>
|
||||
{{topic-status topic=topic disableActions=true}}<span class='topic-title'>{{unbound topic.title}}</span>{{category-badge topic.category}}
|
||||
</span>
|
||||
{{#unless controller.site.mobileView}}
|
||||
<span class='blurb'>
|
||||
{{format-age created_at}} - {{{unbound blurb}}}
|
||||
</span>
|
||||
{{/unless}}
|
||||
</a>
|
||||
@ -1,4 +0,0 @@
|
||||
<a href='{{unbound path}}'>
|
||||
{{avatar this imageSize="small"}}
|
||||
{{unbound username}}
|
||||
</a>
|
||||
@ -17,7 +17,7 @@
|
||||
{{/if}}
|
||||
<li>
|
||||
{{#link-to "discovery.latest" class="latest-topics-link"}}
|
||||
{{i18n 'filters.latest.title'}}
|
||||
{{i18n 'filters.latest.title.zero'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{#if showBadgesLink}}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
{{#if controller.visible}}
|
||||
<div class="card-content">
|
||||
|
||||
<a href {{action "showUser"}}>{{bound-avatar avatar "huge"}}</a>
|
||||
<a href={{user.path}} {{action "showUser"}}>{{bound-avatar avatar "huge"}}</a>
|
||||
|
||||
<div class="names">
|
||||
<span>
|
||||
<h1 class="{{staff}} {{new_user}}">
|
||||
<a href {{action "showUser"}}>{{username}} {{user-status user currentUser=currentUser}}</a>
|
||||
<a href={{user.path}} {{action "showUser"}}>{{username}} {{user-status user currentUser=currentUser}}</a>
|
||||
</h1>
|
||||
|
||||
{{#if user.name}}
|
||||
@ -18,7 +18,7 @@
|
||||
{{/if}}
|
||||
|
||||
{{#if showName}}
|
||||
<h2><a href {{action "showUser"}}>{{name}}</a></h2>
|
||||
<h2><a href={{user.path}} {{action "showUser"}}>{{name}}</a></h2>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -5,9 +5,12 @@ export default ModalBodyView.extend({
|
||||
classNames: ['avatar-selector'],
|
||||
title: I18n.t('user.change_avatar.title'),
|
||||
|
||||
// *HACK* used to select the proper radio button, cause {{action}}
|
||||
// stops the default behavior
|
||||
// *HACK* used to select the proper radio button, because {{action}} stops the default behavior
|
||||
selectedChanged: function() {
|
||||
Em.run.next(() => $('input:radio[name="avatar"]').val([this.get('controller.selected')]) );
|
||||
}.observes('controller.selected')
|
||||
Em.run.next(() => $('input:radio[name="avatar"]').val([this.get('controller.selected')]));
|
||||
}.observes('controller.selected').on("didInsertElement"),
|
||||
|
||||
_focusSelectedButton: function() {
|
||||
Em.run.next(() => $('input:radio[value="' + this.get('controller.selected') + '"]').focus());
|
||||
}.on("didInsertElement")
|
||||
});
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
export default Ember.CollectionView.extend({
|
||||
tagName: 'ul',
|
||||
itemViewClass: Discourse.GroupedView.extend({
|
||||
tagName: 'li',
|
||||
classNameBindings: ['selected'],
|
||||
templateName: Discourse.computed.fmt('parentView.displayType', "search/%@_result")
|
||||
}),
|
||||
didInsertElement: function(){
|
||||
var term = this.get('controller.term');
|
||||
if(!_.isEmpty(term)) {
|
||||
this.$('.blurb').highlight(term.split(/\s+/), {className: 'search-highlight'});
|
||||
this.$('.topic-title').highlight(term.split(/\s+/), {className: 'search-highlight'} );
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,27 +0,0 @@
|
||||
/**
|
||||
This is a text field that supports a dynamic placeholder based on search context.
|
||||
|
||||
@class SearchTextField
|
||||
@extends Discourse.TextField
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
|
||||
import TextField from 'discourse/components/text-field';
|
||||
|
||||
export default TextField.extend({
|
||||
|
||||
/**
|
||||
A dynamic placeholder for the search field based on our context
|
||||
|
||||
@property placeholder
|
||||
**/
|
||||
placeholder: function() {
|
||||
|
||||
if(this.get('searchContextEnabled')){
|
||||
return "";
|
||||
}
|
||||
|
||||
return I18n.t('search.title');
|
||||
}.property('searchContextEnabled')
|
||||
});
|
||||
@ -2,5 +2,11 @@ export default Discourse.View.extend({
|
||||
tagName: 'div',
|
||||
classNames: ['d-dropdown'],
|
||||
elementId: 'search-dropdown',
|
||||
templateName: 'search'
|
||||
templateName: 'search',
|
||||
keyDown: function(e){
|
||||
var term = this.get('controller.term');
|
||||
if (e.which === 13 && term && term.length > 2) {
|
||||
this.get('controller').send('moreOfType', 'topic');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -65,6 +65,10 @@ export default Discourse.View.extend(StringBuffer, {
|
||||
},
|
||||
|
||||
expandPinned: function() {
|
||||
if (this.get('controller.searchTerm')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const pinned = this.get('topic.pinned');
|
||||
if (!pinned) {
|
||||
return false;
|
||||
@ -130,6 +134,22 @@ export default Discourse.View.extend(StringBuffer, {
|
||||
this.set('topic.highlight', false);
|
||||
this.highlight();
|
||||
}
|
||||
|
||||
var term = this.get('controller.searchTerm');
|
||||
const self = this;
|
||||
if (term) {
|
||||
var terms = term.split(/\s+/);
|
||||
// .main-link a is omitted cause a bit clowny
|
||||
var excerpt = self.$('.topic-excerpt');
|
||||
// some sane wrapping
|
||||
excerpt.text(excerpt.text().replace(/\S{40,}/g, function(match){
|
||||
return match.replace(/(\S)/g, "$1\u200B");
|
||||
}));
|
||||
|
||||
terms.forEach(function(word) {
|
||||
excerpt.highlight(word, {element: 'b', className: 'search-highlight'});
|
||||
});
|
||||
}
|
||||
}.on('didInsertElement')
|
||||
|
||||
});
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/upload-customization',
|
||||
title: I18n.t('admin.customize.import_title')
|
||||
});
|
||||
@ -84,18 +84,31 @@ export default Discourse.View.extend(CleansUp, {
|
||||
},
|
||||
|
||||
_willShow(target) {
|
||||
const rtl = ($('html').css('direction')) === 'rtl';
|
||||
if (!target) { return; }
|
||||
const width = this.$().width();
|
||||
|
||||
Ember.run.schedule('afterRender', () => {
|
||||
if (target) {
|
||||
let position = target.offset();
|
||||
if (position) {
|
||||
position.left += target.width() + 10;
|
||||
|
||||
const overage = ($(window).width() - 50) - (position.left + width);
|
||||
if (overage < 0) {
|
||||
position.left += overage;
|
||||
position.top += target.height() + 48;
|
||||
if (rtl) { // The site direction is rtl
|
||||
position.right = $(window).width() - position.left + 10;
|
||||
position.left = 'auto';
|
||||
let overage = ($(window).width() - 50) - (position.right + width);
|
||||
if (overage < 0) {
|
||||
position.right += overage;
|
||||
position.top += target.height() + 48;
|
||||
}
|
||||
} else { // The site direction is ltr
|
||||
position.left += target.width() + 10;
|
||||
|
||||
let overage = ($(window).width() - 50) - (position.left + width);
|
||||
if (overage < 0) {
|
||||
position.left += overage;
|
||||
position.top += target.height() + 48;
|
||||
}
|
||||
}
|
||||
|
||||
position.top -= $('#main-outlet').offset().top;
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
//= require ./discourse/views/cloaked
|
||||
//= require ./discourse/components/combo-box
|
||||
//= require ./discourse/views/button
|
||||
//= require ./discourse/components/search-result
|
||||
//= require ./discourse/components/dropdown-button
|
||||
//= require ./discourse/components/notifications-button
|
||||
//= require ./discourse/components/topic-notifications-button
|
||||
|
||||
@ -545,6 +545,9 @@ section.details {
|
||||
.preview-link {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.export {
|
||||
float: right;
|
||||
}
|
||||
padding-left: 10px;
|
||||
width: 70%;
|
||||
.style-name {
|
||||
|
||||
@ -232,6 +232,13 @@ ol.category-breadcrumb {
|
||||
h2 {
|
||||
float: left;
|
||||
margin: 5px 0 10px;
|
||||
|
||||
.top-date-string {
|
||||
color: scale-color($primary, $lightness: 60%);
|
||||
font-weight: normal;
|
||||
font-size: 0.7em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
|
||||
@ -138,6 +138,55 @@
|
||||
.raw-email-textarea {
|
||||
height: 300px;
|
||||
}
|
||||
.json-uploader {
|
||||
.jsfu-shade-container {
|
||||
display: table-row;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.jsfu-shade {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
.text {
|
||||
color: rgb(255,255,255);
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
font-size: 36px;
|
||||
text-align: center;
|
||||
line-height: 38px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.jsfu-file {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
min-width: 120px;
|
||||
}
|
||||
.jsfu-separator {
|
||||
vertical-align: middle;
|
||||
display: table-cell;
|
||||
font-size: 36px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.jsfu-paste {
|
||||
display: table-cell;
|
||||
width: 100%;
|
||||
textarea {
|
||||
margin-bottom: 0;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.password-confirmation {
|
||||
display: none;
|
||||
|
||||
@ -160,7 +160,7 @@ aside.quote {
|
||||
|
||||
pre {
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: normal;
|
||||
display: block;
|
||||
padding: 5px 10px;
|
||||
color: $primary;
|
||||
@ -197,3 +197,39 @@ blockquote > *:first-child {
|
||||
blockquote > *:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.gap {
|
||||
background-color: dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
padding: 5px 0;
|
||||
color: lighten($primary, 60%);
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
|
||||
&.jagged-border {
|
||||
background-image:
|
||||
linear-gradient(
|
||||
135deg,
|
||||
$secondary 50%, rgba(255,255,255,0) 50%
|
||||
),
|
||||
linear-gradient(
|
||||
-135deg,
|
||||
$secondary 50%, rgba(255,255,255,0) 50%
|
||||
),
|
||||
linear-gradient(
|
||||
45deg,
|
||||
$secondary 50%, rgba(255,255,255,0) 50%
|
||||
),
|
||||
linear-gradient(
|
||||
-45deg,
|
||||
$secondary 50%, rgba(255,255,255,0) 50%
|
||||
);
|
||||
background-position:
|
||||
top center, top center,
|
||||
bottom center, bottom center;
|
||||
background-size: .9em .9em;
|
||||
background-repeat: repeat-x;
|
||||
padding: 20px 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -171,12 +171,12 @@
|
||||
}
|
||||
|
||||
li.bar>.badge-category {
|
||||
background: dark-light-diff($primary, $secondary, 95%, -65%) !important;
|
||||
background: dark-light-diff($primary, $secondary, 90%, -65%) !important;
|
||||
color: $primary !important;
|
||||
}
|
||||
|
||||
li.bullet>.badge-category {
|
||||
background: dark-light-diff($primary, $secondary, 95%, -65%) !important;
|
||||
background: dark-light-diff($primary, $secondary, 90%, -65%) !important;
|
||||
color: $primary !important;
|
||||
|
||||
.badge-category-bg {
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
|
||||
li {
|
||||
font-weight: normal;
|
||||
margin-top: 3px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -74,6 +74,17 @@
|
||||
.posts-count {
|
||||
background-color: scale-color($tertiary, $lightness: -40%);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.search-link {
|
||||
.fa, .blurb {
|
||||
color: scale-color($tertiary, $lightness: -40%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.composer-popup:nth-of-type(2) {
|
||||
@ -288,6 +299,7 @@
|
||||
}
|
||||
|
||||
.submit-panel {
|
||||
width: 28%;
|
||||
position: absolute;
|
||||
display: block;
|
||||
bottom: 8px;
|
||||
|
||||
@ -54,6 +54,7 @@ and (max-width : 570px) {
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
font-size: 11px;
|
||||
line-height: 1.3em;
|
||||
.search-highlight {
|
||||
color: scale-color($primary, $lightness: 25%);
|
||||
}
|
||||
|
||||
@ -242,7 +242,10 @@
|
||||
// Misc. stuff
|
||||
// --------------------------------------------------
|
||||
|
||||
#list-area .top-lists h2 { margin: 5px 0 10px; }
|
||||
#list-area .top-lists h2 {
|
||||
cursor: pointer;
|
||||
margin: 5px 0 10px;
|
||||
}
|
||||
|
||||
#list-area {
|
||||
h2 {
|
||||
|
||||
@ -97,6 +97,7 @@ nav.post-controls {
|
||||
|
||||
.show-replies {
|
||||
margin-left: 0;
|
||||
font-size: inherit;
|
||||
span.badge-posts {color: scale-color($primary, $lightness: 60%);}
|
||||
&:hover {
|
||||
background: dark-light-diff($primary, $secondary, 90%, -65%);
|
||||
@ -104,6 +105,7 @@ nav.post-controls {
|
||||
}
|
||||
i {
|
||||
margin-left: 5px;
|
||||
font-size: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -563,6 +565,7 @@ a.mention {
|
||||
padding: 2px 4px;
|
||||
color: $primary;
|
||||
background: dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
@ -709,11 +712,6 @@ $topic-avatar-width: 45px;
|
||||
}
|
||||
|
||||
.gap {
|
||||
background-color: dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
padding: 5px 0;
|
||||
color: lighten($primary, 30%);
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
width: calc(#{$topic-avatar-width} + #{$topic-body-width} + 2 * #{$topic-body-width-padding});
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,9 @@
|
||||
// --------------------------------------------------
|
||||
|
||||
.list-controls {
|
||||
.category-breadcrumb.hidden {
|
||||
display: none;
|
||||
}
|
||||
margin: 5px;
|
||||
.nav {
|
||||
float: left;
|
||||
@ -17,6 +20,37 @@
|
||||
.btn {
|
||||
float: right;
|
||||
margin-left: 8px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.nav-pills {
|
||||
position: relative;
|
||||
}
|
||||
.nav-pills .drop {
|
||||
border: 1px solid dark-light-diff($primary, $secondary, 90%, -65%);
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
background-color: $secondary;
|
||||
padding: 0 10px 10px 10px;
|
||||
width: 150px;
|
||||
top: 100%;
|
||||
margin: 0;
|
||||
li {
|
||||
list-style-type: none;
|
||||
margin-left: 0;
|
||||
margin-top: 5px;
|
||||
padding-top: 10px;
|
||||
a {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.nav-pills > li {
|
||||
background: dark-light-diff($primary, $secondary, 90%, -65%);
|
||||
i.fa-caret-down {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
.gap {
|
||||
background-color: dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
padding: 5px 15px;
|
||||
color: $primary;
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
/* may not need this */
|
||||
}
|
||||
|
||||
.time-gap {
|
||||
|
||||
border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
color: lighten($primary, 50%);
|
||||
padding-bottom: 3px;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
.topic-avatar {
|
||||
margin: 0 5px 0 10px;
|
||||
}
|
||||
@ -28,9 +25,6 @@ span.badge-posts {
|
||||
|
||||
.show-replies {
|
||||
display: none;
|
||||
i {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
nav.post-controls {
|
||||
|
||||
@ -13,7 +13,12 @@ class Admin::ReportsController < Admin::AdminController
|
||||
end_date = start_date + 1.month
|
||||
end_date = Time.parse(params[:end_date]) if params[:end_date].present?
|
||||
|
||||
report = Report.find(report_type, {start_date: start_date, end_date: end_date})
|
||||
category_id = if params.has_key?(:category_id)
|
||||
params[:category_id].blank? ? SiteSetting.uncategorized_category_id : params[:category_id].to_i
|
||||
end
|
||||
|
||||
report = Report.find(report_type, start_date: start_date, end_date: end_date, category_id: category_id)
|
||||
|
||||
raise Discourse::NotFound if report.blank?
|
||||
|
||||
render_json_dump(report: report)
|
||||
|
||||
@ -2,6 +2,8 @@ class Admin::SiteCustomizationsController < Admin::AdminController
|
||||
|
||||
before_filter :enable_customization
|
||||
|
||||
skip_before_filter :check_xhr, only: [:show]
|
||||
|
||||
def index
|
||||
@site_customizations = SiteCustomization.order(:name)
|
||||
|
||||
@ -48,6 +50,26 @@ class Admin::SiteCustomizationsController < Admin::AdminController
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@site_customization = SiteCustomization.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
check_xhr
|
||||
render json: SiteCustomizationSerializer.new(@site_customization)
|
||||
end
|
||||
|
||||
format.any(:html, :text) do
|
||||
raise RenderEmpty.new if request.xhr?
|
||||
|
||||
response.headers['Content-Disposition'] = "attachment; filename=#{@site_customization.name.parameterize}.dcstyle.json"
|
||||
response.sending_file = true
|
||||
render json: SiteCustomizationSerializer.new(@site_customization)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def site_customization_params
|
||||
|
||||
@ -269,7 +269,8 @@ class ApplicationController < ActionController::Base
|
||||
find_opts[:active] = true unless opts[:include_inactive]
|
||||
User.find_by(find_opts)
|
||||
elsif params[:external_id]
|
||||
SingleSignOnRecord.find_by(external_id: params[:external_id]).try(:user)
|
||||
external_id = params[:external_id].gsub(/\.json$/, '')
|
||||
SingleSignOnRecord.find_by(external_id: external_id).try(:user)
|
||||
end
|
||||
raise Discourse::NotFound if user.blank?
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user