Version bump
This commit is contained in:
commit
6bb2dd0584
@ -42,6 +42,7 @@ before_install:
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-spoiler-alert.git plugins/discourse-spoiler-alert
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-cakeday.git plugins/discourse-cakeday
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-canned-replies.git plugins/discourse-canned-replies
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-slack-official.git plugins/discourse-slack-official
|
||||
- npm i -g eslint babel-eslint
|
||||
- eslint app/assets/javascripts
|
||||
- eslint --ext .es6 app/assets/javascripts
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import CanCheckEmails from 'discourse/mixins/can-check-emails';
|
||||
import { propertyNotEqual, setting } from 'discourse/lib/computed';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Controller.extend(CanCheckEmails, {
|
||||
editingUsername: false,
|
||||
editingName: false,
|
||||
editingTitle: false,
|
||||
originalPrimaryGroupId: null,
|
||||
availableGroups: null,
|
||||
@ -54,23 +58,58 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
anonymize() { return this.get('model').anonymize(); },
|
||||
destroy() { return this.get('model').destroy(); },
|
||||
|
||||
toggleUsernameEdit() {
|
||||
this.set('userUsernameValue', this.get('model.username'));
|
||||
this.toggleProperty('editingUsername');
|
||||
},
|
||||
|
||||
saveUsername() {
|
||||
const oldUsername = this.get('model.username');
|
||||
this.set('model.username', this.get('userUsernameValue'));
|
||||
|
||||
return ajax(`/users/${oldUsername.toLowerCase()}/preferences/username`, {
|
||||
data: { new_username: this.get('userUsernameValue') },
|
||||
type: 'PUT'
|
||||
}).catch(e => {
|
||||
this.set('model.username', oldUsername);
|
||||
popupAjaxError(e);
|
||||
}).finally(() => this.toggleProperty('editingUsername'));
|
||||
},
|
||||
|
||||
toggleNameEdit() {
|
||||
this.set('userNameValue', this.get('model.name'));
|
||||
this.toggleProperty('editingName');
|
||||
},
|
||||
|
||||
saveName() {
|
||||
const oldName = this.get('model.name');
|
||||
this.set('model.name', this.get('userNameValue'));
|
||||
|
||||
return ajax(userPath(`${this.get('model.username').toLowerCase()}.json`), {
|
||||
data: { name: this.get('userNameValue') },
|
||||
type: 'PUT'
|
||||
}).catch(e => {
|
||||
this.set('model.name', oldName);
|
||||
popupAjaxError(e);
|
||||
}).finally(() => this.toggleProperty('editingName'));
|
||||
},
|
||||
|
||||
toggleTitleEdit() {
|
||||
this.set('userTitleValue', this.get('model.title'));
|
||||
this.toggleProperty('editingTitle');
|
||||
},
|
||||
|
||||
saveTitle() {
|
||||
const self = this;
|
||||
const prevTitle = this.get('userTitleValue');
|
||||
|
||||
return ajax(`/users/${this.get('model.username').toLowerCase()}.json`, {
|
||||
this.set('model.title', this.get('userTitleValue'));
|
||||
return ajax(userPath(`${this.get('model.username').toLowerCase()}.json`), {
|
||||
data: {title: this.get('userTitleValue')},
|
||||
type: 'PUT'
|
||||
}).catch(function(e) {
|
||||
bootbox.alert(I18n.t("generic_error_with_reason", {error: "http: " + e.status + " - " + e.body}));
|
||||
}).finally(function() {
|
||||
self.set('model.title', self.get('userTitleValue'));
|
||||
self.toggleProperty('editingTitle');
|
||||
});
|
||||
}).catch(e => {
|
||||
this.set('model.title', prevTitle);
|
||||
popupAjaxError(e);
|
||||
}).finally(() => this.toggleProperty('editingTitle'));
|
||||
},
|
||||
|
||||
generateApiKey() {
|
||||
|
||||
@ -7,7 +7,7 @@ import { observes } from 'ember-addons/ember-computed-decorators';
|
||||
export default Ember.Controller.extend({
|
||||
query: null,
|
||||
queryParams: ['order', 'ascending'],
|
||||
order: 'seen',
|
||||
order: null,
|
||||
ascending: null,
|
||||
showEmails: false,
|
||||
refreshing: false,
|
||||
|
||||
@ -5,6 +5,7 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import ApiKey from 'admin/models/api-key';
|
||||
import Group from 'discourse/models/group';
|
||||
import TL3Requirements from 'admin/models/tl3-requirements';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
const AdminUser = Discourse.User.extend({
|
||||
|
||||
@ -114,11 +115,10 @@ const AdminUser = Discourse.User.extend({
|
||||
},
|
||||
|
||||
revokeAdmin() {
|
||||
const self = this;
|
||||
return ajax("/admin/users/" + this.get('id') + "/revoke_admin", {
|
||||
return ajax(`/admin/users/${this.get('id')}/revoke_admin`, {
|
||||
type: 'PUT'
|
||||
}).then(function() {
|
||||
self.setProperties({
|
||||
}).then(() => {
|
||||
this.setProperties({
|
||||
admin: false,
|
||||
can_grant_admin: true,
|
||||
can_revoke_admin: false
|
||||
@ -127,15 +127,10 @@ const AdminUser = Discourse.User.extend({
|
||||
},
|
||||
|
||||
grantAdmin() {
|
||||
const self = this;
|
||||
return ajax("/admin/users/" + this.get('id') + "/grant_admin", {
|
||||
return ajax(`/admin/users/${this.get('id')}/grant_admin`, {
|
||||
type: 'PUT'
|
||||
}).then(function() {
|
||||
self.setProperties({
|
||||
admin: true,
|
||||
can_grant_admin: false,
|
||||
can_revoke_admin: true
|
||||
});
|
||||
}).then(() => {
|
||||
bootbox.alert(I18n.t("admin.user.grant_admin_confirm"));
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
@ -346,7 +341,7 @@ const AdminUser = Discourse.User.extend({
|
||||
},
|
||||
|
||||
sendActivationEmail() {
|
||||
return ajax('/users/action/send_activation_email', {
|
||||
return ajax(userPath('action/send_activation_email'), {
|
||||
type: 'POST',
|
||||
data: { username: this.get('username') }
|
||||
}).then(function() {
|
||||
|
||||
@ -22,18 +22,40 @@
|
||||
|
||||
<div class='display-row username'>
|
||||
<div class='field'>{{i18n 'user.username.title'}}</div>
|
||||
<div class='value'>{{model.username}}</div>
|
||||
<div class='value'>
|
||||
{{#if editingUsername}}
|
||||
{{text-field value=userUsernameValue autofocus="autofocus"}}
|
||||
{{else}}
|
||||
<span {{action "toggleUsernameEdit"}}>{{model.username}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#link-to 'preferences.username' model class="btn"}}
|
||||
{{fa-icon "pencil"}}
|
||||
{{i18n 'user.change_username.title'}}
|
||||
{{/link-to}}
|
||||
{{#if editingUsername}}
|
||||
{{d-button action="saveUsername" label="admin.user_fields.save"}}
|
||||
<a href {{action "toggleUsernameEdit"}}>{{i18n 'cancel'}}</a>
|
||||
{{else}}
|
||||
{{d-button action="toggleUsernameEdit" icon="pencil"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'user.name.title'}}</div>
|
||||
<div class='value'>{{model.name}}</div>
|
||||
<div class='value'>
|
||||
{{#if editingName}}
|
||||
{{text-field value=userNameValue autofocus="autofocus"}}
|
||||
{{else}}
|
||||
<span {{action "toggleNameEdit"}}>{{model.name}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if editingName}}
|
||||
{{d-button action="saveName" label="admin.user_fields.save"}}
|
||||
<a href {{action "toggleNameEdit"}}>{{i18n 'cancel'}}</a>
|
||||
{{else}}
|
||||
{{d-button action="toggleNameEdit" icon="pencil"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if canCheckEmails}}
|
||||
@ -90,10 +112,10 @@
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if editingTitle}}
|
||||
{{d-button action="saveTitle" label="admin.user.save_title"}}
|
||||
{{d-button action="saveTitle" label="admin.user_fields.save"}}
|
||||
<a href {{action "toggleTitleEdit"}}>{{i18n 'cancel'}}</a>
|
||||
{{else}}
|
||||
{{d-button action="toggleTitleEdit" icon="pencil" label="admin.user.edit_title"}}
|
||||
{{d-button action="toggleTitleEdit" icon="pencil"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
//= require ./discourse/lib/computed
|
||||
//= require ./discourse/lib/formatter
|
||||
//= require ./discourse/lib/eyeline
|
||||
//= require ./discourse/mixins/scrolling
|
||||
//= require ./discourse/lib/show-modal
|
||||
//= require ./discourse/mixins/scrolling
|
||||
//= require ./discourse/models/model
|
||||
//= require ./discourse/models/rest
|
||||
@ -69,7 +69,6 @@
|
||||
//= require ./discourse/lib/emoji/groups
|
||||
//= require ./discourse/lib/emoji/toolbar
|
||||
//= require ./discourse/components/d-editor
|
||||
//= require ./discourse/lib/show-modal
|
||||
//= require ./discourse/lib/screen-track
|
||||
//= require ./discourse/routes/discourse
|
||||
//= require ./discourse/routes/build-topic-route
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import { observes } from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
limited: false,
|
||||
autoCloseValid: false,
|
||||
|
||||
@computed("limited")
|
||||
autoCloseUnits(limited) {
|
||||
const key = limited ? "composer.auto_close.limited.units" : "composer.auto_close.all.units";
|
||||
return I18n.t(key);
|
||||
},
|
||||
|
||||
@computed("limited")
|
||||
autoCloseExamples(limited) {
|
||||
const key = limited ? "composer.auto_close.limited.examples" : "composer.auto_close.all.examples";
|
||||
return I18n.t(key);
|
||||
},
|
||||
|
||||
@observes("autoCloseTime", "limited")
|
||||
_updateAutoCloseValid() {
|
||||
const limited = this.get("limited"),
|
||||
autoCloseTime = this.get("autoCloseTime"),
|
||||
isValid = this._isAutoCloseValid(autoCloseTime, limited);
|
||||
this.set("autoCloseValid", isValid);
|
||||
},
|
||||
|
||||
_isAutoCloseValid(autoCloseTime, limited) {
|
||||
const t = (autoCloseTime || "").toString().trim();
|
||||
if (t.length === 0) {
|
||||
// "empty" is always valid
|
||||
return true;
|
||||
} else if (limited) {
|
||||
// only # of hours in limited mode
|
||||
return t.match(/^(\d+\.)?\d+$/);
|
||||
} else {
|
||||
if (t.match(/^\d{4}-\d{1,2}-\d{1,2}(?: \d{1,2}:\d{2}(\s?[AP]M)?){0,1}$/i)) {
|
||||
// timestamp must be in the future
|
||||
return moment(t).isAfter();
|
||||
} else {
|
||||
// either # of hours or absolute time
|
||||
return (t.match(/^(\d+\.)?\d+$/) || t.match(/^\d{1,2}:\d{2}(\s?[AP]M)?$/i)) !== null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,47 @@
|
||||
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
limited: false,
|
||||
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
this._updateInputValid();
|
||||
},
|
||||
|
||||
@computed("limited")
|
||||
inputUnitsKey(limited) {
|
||||
return limited ? "topic.auto_update_input.limited.units" : "topic.auto_update_input.all.units";
|
||||
},
|
||||
|
||||
@computed("limited")
|
||||
inputExamplesKey(limited) {
|
||||
return limited ? "topic.auto_update_input.limited.examples" : "topic.auto_update_input.all.examples";
|
||||
},
|
||||
|
||||
@observes("input", "limited")
|
||||
_updateInputValid() {
|
||||
this.set(
|
||||
"inputValid", this._isInputValid(this.get("input"), this.get("limited"))
|
||||
);
|
||||
},
|
||||
|
||||
_isInputValid(input, limited) {
|
||||
const t = (input || "").toString().trim();
|
||||
|
||||
if (t.length === 0) {
|
||||
return true;
|
||||
// "empty" is always valid
|
||||
} else if (limited) {
|
||||
// only # of hours in limited mode
|
||||
return t.match(/^(\d+\.)?\d+$/);
|
||||
} else {
|
||||
if (t.match(/^\d{4}-\d{1,2}-\d{1,2}(?: \d{1,2}:\d{2}(\s?[AP]M)?){0,1}$/i)) {
|
||||
// timestamp must be in the future
|
||||
return moment(t).isAfter();
|
||||
} else {
|
||||
// either # of hours or absolute time
|
||||
return (t.match(/^(\d+\.)?\d+$/) || t.match(/^\d{1,2}:\d{2}(\s?[AP]M)?$/i)) !== null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -3,6 +3,7 @@ import { categoryBadgeHTML } from 'discourse/helpers/category-link';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { observes, on } from 'ember-addons/ember-computed-decorators';
|
||||
import PermissionType from 'discourse/models/permission-type';
|
||||
import Category from 'discourse/models/category';
|
||||
|
||||
export default ComboboxView.extend({
|
||||
classNames: ['combobox category-combobox'],
|
||||
@ -14,13 +15,16 @@ export default ComboboxView.extend({
|
||||
content(scopedCategoryId, categories) {
|
||||
// Always scope to the parent of a category, if present
|
||||
if (scopedCategoryId) {
|
||||
const scopedCat = Discourse.Category.findById(scopedCategoryId);
|
||||
const scopedCat = Category.findById(scopedCategoryId);
|
||||
scopedCategoryId = scopedCat.get('parent_category_id') || scopedCat.get('id');
|
||||
}
|
||||
|
||||
const excludeCategoryId = this.get('excludeCategoryId');
|
||||
|
||||
return categories.filter(c => {
|
||||
if (scopedCategoryId && c.get('id') !== scopedCategoryId && c.get('parent_category_id') !== scopedCategoryId) { return false; }
|
||||
if (c.get('isUncategorizedCategory')) { return false; }
|
||||
const categoryId = c.get('id');
|
||||
if (scopedCategoryId && categoryId !== scopedCategoryId && c.get('parent_category_id') !== scopedCategoryId) { return false; }
|
||||
if (c.get('isUncategorizedCategory') || excludeCategoryId === categoryId) { return false; }
|
||||
return c.get('permission') === PermissionType.FULL;
|
||||
});
|
||||
},
|
||||
@ -30,19 +34,19 @@ export default ComboboxView.extend({
|
||||
_updateCategories() {
|
||||
if (!this.get('categories')) {
|
||||
const categories = Discourse.SiteSettings.fixed_category_positions_on_create ?
|
||||
Discourse.Category.list() :
|
||||
Discourse.Category.listByActivity();
|
||||
Category.list() :
|
||||
Category.listByActivity();
|
||||
this.set('categories', categories);
|
||||
}
|
||||
},
|
||||
|
||||
@computed("rootNone")
|
||||
none(rootNone) {
|
||||
@computed("rootNone", "rootNoneLabel")
|
||||
none(rootNone, rootNoneLabel) {
|
||||
if (Discourse.SiteSettings.allow_uncategorized_topics || this.get('allowUncategorized')) {
|
||||
if (rootNone) {
|
||||
return "category.none";
|
||||
return rootNoneLabel || "category.none";
|
||||
} else {
|
||||
return Discourse.Category.findUncategorized();
|
||||
return Category.findUncategorized();
|
||||
}
|
||||
} else {
|
||||
return 'category.choose';
|
||||
@ -54,12 +58,12 @@ export default ComboboxView.extend({
|
||||
|
||||
// If we have no id, but text with the uncategorized name, we can use that badge.
|
||||
if (Ember.isEmpty(item.id)) {
|
||||
const uncat = Discourse.Category.findUncategorized();
|
||||
const uncat = Category.findUncategorized();
|
||||
if (uncat && uncat.get('name') === item.text) {
|
||||
category = uncat;
|
||||
}
|
||||
} else {
|
||||
category = Discourse.Category.findById(parseInt(item.id,10));
|
||||
category = Category.findById(parseInt(item.id,10));
|
||||
}
|
||||
|
||||
if (!category) return item.text;
|
||||
@ -67,7 +71,7 @@ export default ComboboxView.extend({
|
||||
const parentCategoryId = category.get('parent_category_id');
|
||||
|
||||
if (parentCategoryId) {
|
||||
result = categoryBadgeHTML(Discourse.Category.findById(parentCategoryId), {link: false}) + " " + result;
|
||||
result = categoryBadgeHTML(Category.findById(parentCategoryId), {link: false}) + " " + result;
|
||||
}
|
||||
|
||||
result += ` <span class='topic-count'>× ${category.get('topic_count')}</span>`;
|
||||
|
||||
@ -3,6 +3,7 @@ import { default as computed, on } from 'ember-addons/ember-computed-decorators'
|
||||
import { linkSeenMentions, fetchUnseenMentions } from 'discourse/lib/link-mentions';
|
||||
import { linkSeenCategoryHashtags, fetchUnseenCategoryHashtags } from 'discourse/lib/link-category-hashtags';
|
||||
import { linkSeenTagHashtags, fetchUnseenTagHashtags } from 'discourse/lib/link-tag-hashtag';
|
||||
import Composer from 'discourse/models/composer';
|
||||
import { load } from 'pretty-text/oneboxer';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import InputValidation from 'discourse/models/input-validation';
|
||||
@ -138,7 +139,7 @@ export default Ember.Component.extend({
|
||||
_renderUnseenMentions($preview, unseen) {
|
||||
// 'Create a New Topic' scenario is not supported (per conversation with codinghorror)
|
||||
// https://meta.discourse.org/t/taking-another-1-7-release-task/51986/7
|
||||
fetchUnseenMentions(unseen, this.get('topic.id')).then(() => {
|
||||
fetchUnseenMentions(unseen, this.get('composer.topic.id')).then(() => {
|
||||
linkSeenMentions($preview, this.siteSettings);
|
||||
this._warnMentionedGroups($preview);
|
||||
this._warnCannotSeeMention($preview);
|
||||
@ -187,13 +188,25 @@ export default Ember.Component.extend({
|
||||
},
|
||||
|
||||
_warnCannotSeeMention($preview) {
|
||||
const composerDraftKey = this.get('composer.draftKey');
|
||||
|
||||
if (composerDraftKey === Composer.CREATE_TOPIC ||
|
||||
composerDraftKey === Composer.NEW_PRIVATE_MESSAGE_KEY ||
|
||||
composerDraftKey === Composer.REPLY_AS_NEW_TOPIC_KEY ||
|
||||
composerDraftKey === Composer.REPLY_AS_NEW_PRIVATE_MESSAGE_KEY) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Ember.run.scheduleOnce('afterRender', () => {
|
||||
var found = this.get('warnedCannotSeeMentions') || [];
|
||||
let found = this.get('warnedCannotSeeMentions') || [];
|
||||
|
||||
$preview.find('.mention.cannot-see').each((idx,e) => {
|
||||
const $e = $(e);
|
||||
var name = $e.data('name');
|
||||
let name = $e.data('name');
|
||||
|
||||
if (found.indexOf(name) === -1) {
|
||||
this.sendAction('cannotSeeMention', [{name: name}]);
|
||||
this.sendAction('cannotSeeMention', [{ name: name }]);
|
||||
found.push(name);
|
||||
}
|
||||
});
|
||||
|
||||
@ -37,7 +37,7 @@ export default Ember.Component.extend(AddArchetypeClass, Scrolling, {
|
||||
const enteredAt = this.get('enteredAt');
|
||||
if (enteredAt && (this.get('lastEnteredAt') !== enteredAt)) {
|
||||
this._lastShowTopic = null;
|
||||
this.scrolled();
|
||||
Ember.run.schedule('afterRender', () => this.scrolled());
|
||||
this.set('lastEnteredAt', enteredAt);
|
||||
}
|
||||
},
|
||||
@ -131,18 +131,22 @@ export default Ember.Component.extend(AddArchetypeClass, Scrolling, {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.set('hasScrolled', offset > 0);
|
||||
|
||||
const topic = this.get('topic');
|
||||
const showTopic = this.showTopicInHeader(topic, offset);
|
||||
if (showTopic !== this._lastShowTopic) {
|
||||
this._lastShowTopic = showTopic;
|
||||
|
||||
if (showTopic) {
|
||||
this.appEvents.trigger('header:show-topic', topic);
|
||||
this._lastShowTopic = true;
|
||||
} else {
|
||||
if (!DiscourseURL.isJumpScheduled()) {
|
||||
this.appEvents.trigger('header:hide-topic');
|
||||
const loadingNear = topic.get('postStream.loadingNearPost') || 1;
|
||||
if (loadingNear === 1) {
|
||||
this.appEvents.trigger('header:hide-topic');
|
||||
this._lastShowTopic = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ export default DropdownButton.extend({
|
||||
id: l.id,
|
||||
title: I18n.t(`${start}.title`),
|
||||
description: I18n.t(`${start}.description`),
|
||||
styleClasses: `${l.key} fa fa-${l.icon}`
|
||||
styleClasses: `${l.key.dasherize()} fa fa-${l.icon}`
|
||||
};
|
||||
});
|
||||
},
|
||||
@ -31,7 +31,7 @@ export default DropdownButton.extend({
|
||||
text(notificationLevel) {
|
||||
const details = buttonDetails(notificationLevel);
|
||||
const { key } = details;
|
||||
const icon = iconHTML(details.icon, { class: key });
|
||||
const icon = iconHTML(details.icon, { class: key.dasherize() });
|
||||
|
||||
if (this.get('buttonIncludesText')) {
|
||||
const prefix = this.get('i18nPrefix');
|
||||
|
||||
@ -55,7 +55,8 @@ export default Ember.Component.extend({
|
||||
const { isIOS, isAndroid, isSafari } = this.capabilities;
|
||||
const showAtEnd = isMobileDevice || isIOS || isAndroid;
|
||||
|
||||
// used to work around Safari losing selection
|
||||
// Don't mess with the original range as it results in weird behaviours
|
||||
// where certain browsers will deselect the selection
|
||||
const clone = firstRange.cloneRange();
|
||||
|
||||
// create a marker element containing a single invisible character
|
||||
@ -63,9 +64,9 @@ export default Ember.Component.extend({
|
||||
markerElement.appendChild(document.createTextNode("\ufeff"));
|
||||
|
||||
// on mobile, collapse the range at the end of the selection
|
||||
if (showAtEnd) { firstRange.collapse(); }
|
||||
if (showAtEnd) { clone.collapse(); }
|
||||
// insert the marker
|
||||
firstRange.insertNode(markerElement);
|
||||
clone.insertNode(markerElement);
|
||||
|
||||
// retrieve the position of the marker
|
||||
const $markerElement = $(markerElement);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { autoUpdatingRelativeAge } from 'discourse/lib/formatter';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export function actionDescriptionHtml(actionCode, createdAt, username) {
|
||||
const dt = new Date(createdAt);
|
||||
@ -9,7 +10,7 @@ export function actionDescriptionHtml(actionCode, createdAt, username) {
|
||||
if (actionCode === "invited_group" || actionCode === "removed_group") {
|
||||
who = `<a class="mention-group" href="/groups/${username}">@${username}</a>`;
|
||||
} else {
|
||||
who = `<a class="mention" href="/users/${username}">@${username}</a>`;
|
||||
who = `<a class="mention" href="${userPath(username)}">@${username}</a>`;
|
||||
}
|
||||
}
|
||||
return I18n.t(`action_codes.${actionCode}`, { who, when }).htmlSafe();
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
import { bufferedRender } from 'discourse-common/lib/buffered-render';
|
||||
|
||||
export default Ember.Component.extend(bufferedRender({
|
||||
elementId: 'topic-closing-info',
|
||||
delayedRerender: null,
|
||||
|
||||
rerenderTriggers: ['topic.closed',
|
||||
'topic.details.auto_close_at',
|
||||
'topic.details.auto_close_based_on_last_post',
|
||||
'topic.details.auto_close_hours'],
|
||||
|
||||
buildBuffer(buffer) {
|
||||
if (!!Ember.isEmpty(this.get('topic.details.auto_close_at'))) return;
|
||||
if (this.get("topic.closed")) return;
|
||||
|
||||
var autoCloseAt = moment(this.get('topic.details.auto_close_at'));
|
||||
if (autoCloseAt < new Date()) return;
|
||||
|
||||
var duration = moment.duration(autoCloseAt - moment());
|
||||
var minutesLeft = duration.asMinutes();
|
||||
var timeLeftString = duration.humanize(true);
|
||||
var rerenderDelay = 1000;
|
||||
|
||||
if (minutesLeft > 2160) {
|
||||
rerenderDelay = 12 * 60 * 60000;
|
||||
} else if (minutesLeft > 1410) {
|
||||
rerenderDelay = 60 * 60000;
|
||||
} else if (minutesLeft > 90) {
|
||||
rerenderDelay = 30 * 60000;
|
||||
} else if (minutesLeft > 2) {
|
||||
rerenderDelay = 60000;
|
||||
}
|
||||
|
||||
var basedOnLastPost = this.get("topic.details.auto_close_based_on_last_post");
|
||||
var key = basedOnLastPost ? 'topic.auto_close_notice_based_on_last_post' : 'topic.auto_close_notice';
|
||||
var autoCloseHours = this.get("topic.details.auto_close_hours") || 0;
|
||||
|
||||
buffer.push('<h3><i class="fa fa-clock-o"></i> ');
|
||||
buffer.push( I18n.t(key, { timeLeft: timeLeftString, duration: moment.duration(autoCloseHours, "hours").humanize() }) );
|
||||
buffer.push('</h3>');
|
||||
|
||||
// TODO Sam: concerned this can cause a heavy rerender loop
|
||||
this.set('delayedRerender', Em.run.later(this, this.rerender, rerenderDelay));
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
if( this.delayedRerender ) {
|
||||
Em.run.cancel(this.get('delayedRerender'));
|
||||
}
|
||||
}
|
||||
}));
|
||||
@ -94,11 +94,15 @@ export default Ember.Component.extend(CleansUp, {
|
||||
|
||||
actions: {
|
||||
enterTop() {
|
||||
DiscourseURL.routeTo(this.get('topic.url'));
|
||||
const topic = this.get('topic');
|
||||
this.appEvents.trigger('header:update-topic', topic);
|
||||
DiscourseURL.routeTo(topic.get('url'));
|
||||
},
|
||||
|
||||
enterBottom() {
|
||||
DiscourseURL.routeTo(this.get('topic.lastPostUrl'));
|
||||
const topic = this.get('topic');
|
||||
this.appEvents.trigger('header:update-topic', topic);
|
||||
DiscourseURL.routeTo(topic.get('lastPostUrl'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -3,7 +3,12 @@ import showModal from 'discourse/lib/show-modal';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
composerOpen: null,
|
||||
info: Em.Object.create(),
|
||||
info: null,
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
this.set('info', Ember.Object.create());
|
||||
},
|
||||
|
||||
_performCheckSize() {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) { return; }
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
import { bufferedRender } from 'discourse-common/lib/buffered-render';
|
||||
import Category from 'discourse/models/category';
|
||||
|
||||
export default Ember.Component.extend(bufferedRender({
|
||||
elementId: 'topic-status-info',
|
||||
delayedRerender: null,
|
||||
|
||||
rerenderTriggers: [
|
||||
'topic.topic_status_update',
|
||||
'topic.topic_status_update.execute_at',
|
||||
'topic.topic_status_update.based_on_last_post',
|
||||
'topic.topic_status_update.duration',
|
||||
'topic.topic_status_update.category_id',
|
||||
],
|
||||
|
||||
buildBuffer(buffer) {
|
||||
if (!this.get('topic.topic_status_update.execute_at')) return;
|
||||
|
||||
let statusUpdateAt = moment(this.get('topic.topic_status_update.execute_at'));
|
||||
if (statusUpdateAt < new Date()) return;
|
||||
|
||||
let duration = moment.duration(statusUpdateAt - moment());
|
||||
let minutesLeft = duration.asMinutes();
|
||||
let rerenderDelay = 1000;
|
||||
|
||||
if (minutesLeft > 2160) {
|
||||
rerenderDelay = 12 * 60 * 60000;
|
||||
} else if (minutesLeft > 1410) {
|
||||
rerenderDelay = 60 * 60000;
|
||||
} else if (minutesLeft > 90) {
|
||||
rerenderDelay = 30 * 60000;
|
||||
} else if (minutesLeft > 2) {
|
||||
rerenderDelay = 60000;
|
||||
}
|
||||
|
||||
let autoCloseHours = this.get("topic.topic_status_update.duration") || 0;
|
||||
|
||||
buffer.push('<h3><i class="fa fa-clock-o"></i> ');
|
||||
|
||||
let options = {
|
||||
timeLeft: duration.humanize(true),
|
||||
duration: moment.duration(autoCloseHours, "hours").humanize(),
|
||||
};
|
||||
|
||||
const categoryId = this.get('topic.topic_status_update.category_id');
|
||||
|
||||
if (categoryId) {
|
||||
const category = Category.findById(categoryId);
|
||||
|
||||
options = _.assign({
|
||||
categoryName: category.get('slug'),
|
||||
categoryUrl: category.get('url')
|
||||
}, options);
|
||||
}
|
||||
|
||||
buffer.push(I18n.t(this._noticeKey(), options));
|
||||
buffer.push('</h3>');
|
||||
|
||||
// TODO Sam: concerned this can cause a heavy rerender loop
|
||||
this.set('delayedRerender', Em.run.later(this, this.rerender, rerenderDelay));
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
if( this.delayedRerender ) {
|
||||
Em.run.cancel(this.get('delayedRerender'));
|
||||
}
|
||||
},
|
||||
|
||||
_noticeKey() {
|
||||
const statusType = this.get('topic.topic_status_update.status_type');
|
||||
|
||||
if (this.get("topic.topic_status_update.based_on_last_post")) {
|
||||
return `topic.status_update_notice.auto_${statusType}_based_on_last_post`;
|
||||
} else {
|
||||
return `topic.status_update_notice.auto_${statusType}`;
|
||||
}
|
||||
}
|
||||
}));
|
||||
@ -5,6 +5,7 @@ import afterTransition from 'discourse/lib/after-transition';
|
||||
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import User from 'discourse/models/user';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
const clickOutsideEventName = "mousedown.outside-user-card";
|
||||
const clickDataExpand = "click.discourse-user-card";
|
||||
@ -92,7 +93,7 @@ export default Ember.Component.extend(CleansUp, {
|
||||
|
||||
// Don't show on mobile
|
||||
if (this.site.mobileView) {
|
||||
DiscourseURL.routeTo(`/users/${username}`);
|
||||
DiscourseURL.routeTo(userPath(username));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { url } from 'discourse/lib/computed';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
function normalize(name) {
|
||||
return name.replace(/[\-\_ \.]/g, '').toLowerCase();
|
||||
@ -8,7 +8,11 @@ function normalize(name) {
|
||||
export default Ember.Component.extend({
|
||||
classNameBindings: [':user-info', 'size'],
|
||||
size: 'small',
|
||||
userPath: url('user.username', '/users/%@'),
|
||||
|
||||
@computed('user.username')
|
||||
userPath(username) {
|
||||
return userPath(username);
|
||||
},
|
||||
|
||||
// TODO: In later ember releases `hasBlock` works without this
|
||||
hasBlock: Ember.computed.alias('template'),
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { extractError } from 'discourse/lib/ajax-error';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
login: Ember.inject.controller(),
|
||||
|
||||
currentEmail: null,
|
||||
newEmail: null,
|
||||
password: null,
|
||||
|
||||
@computed('newEmail', 'currentEmail')
|
||||
submitDisabled(newEmail, currentEmail) {
|
||||
return newEmail === currentEmail;
|
||||
},
|
||||
|
||||
actions: {
|
||||
changeEmail() {
|
||||
const login = this.get('login');
|
||||
|
||||
ajax(userPath('update-activation-email'), {
|
||||
data: {
|
||||
username: login.get('loginName'),
|
||||
password: login.get('loginPassword'),
|
||||
email: this.get('newEmail')
|
||||
},
|
||||
type: 'PUT'
|
||||
}).then(() => {
|
||||
const modal = this.showModal('activation-resent', {title: 'log_in'});
|
||||
modal.set('currentEmail', this.get('newEmail'));
|
||||
}).catch(err => this.flash(extractError(err), 'error'));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
modal: null
|
||||
});
|
||||
@ -367,7 +367,7 @@ export default Ember.Controller.extend({
|
||||
|
||||
cannotSeeMention(mentions) {
|
||||
mentions.forEach(mention => {
|
||||
const translation = (this.get('topic.isPrivateMessage')) ?
|
||||
const translation = (this.get('model.topic.isPrivateMessage')) ?
|
||||
'composer.cannot_see_mention.private' :
|
||||
'composer.cannot_see_mention.category';
|
||||
const body = I18n.t(translation, {
|
||||
|
||||
@ -6,6 +6,7 @@ import { emailValid } from 'discourse/lib/utilities';
|
||||
import InputValidation from 'discourse/models/input-validation';
|
||||
import PasswordValidation from "discourse/mixins/password-validation";
|
||||
import UsernameValidation from "discourse/mixins/username-validation";
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, UsernameValidation, {
|
||||
login: Ember.inject.controller(),
|
||||
@ -164,7 +165,7 @@ export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, U
|
||||
|
||||
@on('init')
|
||||
fetchConfirmationValue() {
|
||||
return ajax('/users/hp.json').then(json => {
|
||||
return ajax(userPath('hp.json')).then(json => {
|
||||
this.set('accountPasswordConfirm', json.value);
|
||||
this.set('accountChallenge', json.challenge.split("").reverse().join(""));
|
||||
});
|
||||
@ -196,7 +197,7 @@ export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, U
|
||||
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'));
|
||||
$hidden_login_form.find('input[name=redirect]').val(userPath('account-created'));
|
||||
$hidden_login_form.submit();
|
||||
} else {
|
||||
self.flash(result.message || I18n.t('create_account.failed'), 'error');
|
||||
|
||||
@ -3,6 +3,7 @@ import { queryParams } from 'discourse/controllers/discovery-sortable';
|
||||
import BulkTopicSelection from 'discourse/mixins/bulk-topic-selection';
|
||||
import { endWith } from 'discourse/lib/computed';
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
const controllerOpts = {
|
||||
discovery: Ember.inject.controller(),
|
||||
@ -133,14 +134,14 @@ const controllerOpts = {
|
||||
}.property('allLoaded', 'model.topics.length'),
|
||||
|
||||
footerEducation: function() {
|
||||
if (!this.get('allLoaded') || this.get('model.topics.length') > 0 || !Discourse.User.current()) { return; }
|
||||
if (!this.get('allLoaded') || this.get('model.topics.length') > 0 || !this.currentUser) { return; }
|
||||
|
||||
const split = (this.get('model.filter') || '').split('/');
|
||||
|
||||
if (split[0] !== 'new' && split[0] !== 'unread') { return; }
|
||||
|
||||
return I18n.t("topics.none.educate." + split[0], {
|
||||
userPrefsUrl: Discourse.getURL("/users/") + (Discourse.User.currentProp("username_lower")) + "/preferences"
|
||||
userPrefsUrl: userPath(`${this.currentUser.get('username_lower')}/preferences`)
|
||||
});
|
||||
}.property('allLoaded', 'model.topics.length')
|
||||
|
||||
|
||||
@ -1,78 +0,0 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { observes } from "ember-addons/ember-computed-decorators";
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
|
||||
// Modal related to auto closing of topics
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
auto_close_valid: true,
|
||||
auto_close_invalid: Em.computed.not('auto_close_valid'),
|
||||
disable_submit: Em.computed.or('auto_close_invalid', 'loading'),
|
||||
loading: false,
|
||||
|
||||
@observes("model.details.auto_close_at", "model.details.auto_close_hours")
|
||||
setAutoCloseTime() {
|
||||
let autoCloseTime = null;
|
||||
|
||||
if (this.get("model.details.auto_close_based_on_last_post")) {
|
||||
autoCloseTime = this.get("model.details.auto_close_hours");
|
||||
} else if (this.get("model.details.auto_close_at")) {
|
||||
const closeTime = new Date(this.get("model.details.auto_close_at"));
|
||||
if (closeTime > new Date()) {
|
||||
autoCloseTime = moment(closeTime).format("YYYY-MM-DD HH:mm");
|
||||
}
|
||||
}
|
||||
|
||||
this.set("model.auto_close_time", autoCloseTime);
|
||||
},
|
||||
|
||||
actions: {
|
||||
saveAutoClose() { this.setAutoClose(this.get("model.auto_close_time")); },
|
||||
removeAutoClose() { this.setAutoClose(null); }
|
||||
},
|
||||
|
||||
setAutoClose(time) {
|
||||
const self = this;
|
||||
this.set('loading', true);
|
||||
ajax({
|
||||
url: `/t/${this.get('model.id')}/autoclose`,
|
||||
type: 'PUT',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
auto_close_time: time,
|
||||
auto_close_based_on_last_post: this.get("model.details.auto_close_based_on_last_post"),
|
||||
timezone_offset: (new Date().getTimezoneOffset())
|
||||
}
|
||||
}).then(result => {
|
||||
self.set('loading', false);
|
||||
if (result.success) {
|
||||
this.send('closeModal');
|
||||
this.set('model.details.auto_close_at', result.auto_close_at);
|
||||
this.set('model.details.auto_close_hours', result.auto_close_hours);
|
||||
} else {
|
||||
bootbox.alert(I18n.t('composer.auto_close.error'));
|
||||
}
|
||||
}).catch(() => {
|
||||
// TODO - incorrectly responds to network errors as bad input
|
||||
bootbox.alert(I18n.t('composer.auto_close.error'));
|
||||
self.set('loading', false);
|
||||
});
|
||||
},
|
||||
|
||||
willCloseImmediately: function() {
|
||||
if (!this.get('model.details.auto_close_based_on_last_post')) {
|
||||
return false;
|
||||
}
|
||||
let closeDate = new Date(this.get('model.last_posted_at'));
|
||||
closeDate.setHours(closeDate.getHours() + this.get('model.auto_close_time'));
|
||||
return closeDate < new Date();
|
||||
}.property('model.details.auto_close_based_on_last_post', 'model.auto_close_time', 'model.last_posted_at'),
|
||||
|
||||
willCloseI18n: function() {
|
||||
if (this.get('model.details.auto_close_based_on_last_post')) {
|
||||
let closeDate = new Date(this.get('model.last_posted_at'));
|
||||
let diff = Math.round((new Date() - closeDate)/(1000*60*60));
|
||||
return I18n.t('topic.auto_close_immediate', {count: diff});
|
||||
}
|
||||
}.property('model.details.auto_close_based_on_last_post', 'model.last_posted_at')
|
||||
|
||||
});
|
||||
@ -0,0 +1,122 @@
|
||||
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import TopicStatusUpdate from 'discourse/models/topic-status-update';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
const CLOSE_STATUS_TYPE = 'close';
|
||||
const OPEN_STATUS_TYPE = 'open';
|
||||
const PUBLISH_TO_CATEGORY_STATUS_TYPE = 'publish_to_category';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
closeStatusType: CLOSE_STATUS_TYPE,
|
||||
openStatusType: OPEN_STATUS_TYPE,
|
||||
publishToCategoryStatusType: PUBLISH_TO_CATEGORY_STATUS_TYPE,
|
||||
updateTimeValid: null,
|
||||
updateTimeInvalid: Em.computed.not('updateTimeValid'),
|
||||
loading: false,
|
||||
updateTime: null,
|
||||
topicStatusUpdate: Ember.computed.alias("model.topic_status_update"),
|
||||
selection: Ember.computed.alias('model.topic_status_update.status_type'),
|
||||
autoOpen: Ember.computed.equal('selection', OPEN_STATUS_TYPE),
|
||||
autoClose: Ember.computed.equal('selection', CLOSE_STATUS_TYPE),
|
||||
publishToCategory: Ember.computed.equal('selection', PUBLISH_TO_CATEGORY_STATUS_TYPE),
|
||||
|
||||
@computed('autoClose', 'updateTime')
|
||||
disableAutoClose(autoClose, updateTime) {
|
||||
return updateTime && !autoClose;
|
||||
},
|
||||
|
||||
@computed('autoOpen', 'updateTime')
|
||||
disableAutoOpen(autoOpen, updateTime) {
|
||||
return updateTime && !autoOpen;
|
||||
},
|
||||
|
||||
@computed('publishToCatgory', 'updateTime')
|
||||
disablePublishToCategory(publishToCatgory, updateTime) {
|
||||
return updateTime && !publishToCatgory;
|
||||
},
|
||||
|
||||
@computed('topicStatusUpdate.based_on_last_post', 'updateTime', 'model.last_posted_at')
|
||||
willCloseImmediately(basedOnLastPost, updateTime, lastPostedAt) {
|
||||
if (!basedOnLastPost) {
|
||||
return false;
|
||||
}
|
||||
const closeDate = new Date(lastPostedAt);
|
||||
closeDate.setHours(closeDate.getHours() + updateTime);
|
||||
return closeDate < new Date();
|
||||
},
|
||||
|
||||
@computed('topicStatusUpdate.based_on_last_post', 'model.last_posted_at')
|
||||
willCloseI18n(basedOnLastPost, lastPostedAt) {
|
||||
if (basedOnLastPost) {
|
||||
const diff = Math.round((new Date() - new Date(lastPostedAt)) / (1000*60*60));
|
||||
return I18n.t('topic.auto_close_immediate', { count: diff });
|
||||
}
|
||||
},
|
||||
|
||||
@computed('updateTime', 'updateTimeInvalid', 'loading')
|
||||
saveDisabled(updateTime, updateTimeInvalid, loading) {
|
||||
return Ember.isEmpty(updateTime) || updateTimeInvalid || loading;
|
||||
},
|
||||
|
||||
@computed("model.visible")
|
||||
excludeCategoryId(visible) {
|
||||
if (visible) return this.get('model.category_id');
|
||||
},
|
||||
|
||||
@observes("topicStatusUpdate.execute_at", "topicStatusUpdate.duration")
|
||||
_setUpdateTime() {
|
||||
let time = null;
|
||||
|
||||
if (this.get("topicStatusUpdate.based_on_last_post")) {
|
||||
time = this.get("topicStatusUpdate.duration");
|
||||
} else if (this.get("topicStatusUpdate.execute_at")) {
|
||||
const closeTime = new Date(this.get("topicStatusUpdate.execute_at"));
|
||||
|
||||
if (closeTime > new Date()) {
|
||||
time = moment(closeTime).format("YYYY-MM-DD HH:mm");
|
||||
}
|
||||
}
|
||||
|
||||
this.set("updateTime", time);
|
||||
},
|
||||
|
||||
_setStatusUpdate(time, status_type) {
|
||||
this.set('loading', true);
|
||||
|
||||
TopicStatusUpdate.updateStatus(
|
||||
this.get('model.id'),
|
||||
time,
|
||||
this.get('topicStatusUpdate.based_on_last_post'),
|
||||
status_type,
|
||||
this.get('categoryId')
|
||||
).then(result => {
|
||||
if (time) {
|
||||
this.send('closeModal');
|
||||
|
||||
this.get("topicStatusUpdate").setProperties({
|
||||
execute_at: result.execute_at,
|
||||
duration: result.duration,
|
||||
category_id: result.category_id
|
||||
});
|
||||
|
||||
this.set('model.closed', result.closed);
|
||||
} else {
|
||||
this.set('topicStatusUpdate', Ember.Object.create({}));
|
||||
this.set('selection', null);
|
||||
}
|
||||
}).catch(error => {
|
||||
popupAjaxError(error);
|
||||
}).finally(() => this.set('loading', false));
|
||||
},
|
||||
|
||||
actions: {
|
||||
saveStatusUpdate() {
|
||||
this._setStatusUpdate(this.get("updateTime"), this.get('selection'));
|
||||
},
|
||||
|
||||
removeStatusUpdate() {
|
||||
this._setStatusUpdate(null, this.get('selection'));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -11,6 +11,8 @@ const SortOrders = [
|
||||
{name: I18n.t('search.latest_post'), id: 1, term: 'order:latest'},
|
||||
{name: I18n.t('search.most_liked'), id: 2, term: 'order:likes'},
|
||||
{name: I18n.t('search.most_viewed'), id: 3, term: 'order:views'},
|
||||
{name: I18n.t('search.latest_topic'), id: 4, term: 'order:latest_topic'},
|
||||
|
||||
];
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
@ -73,14 +75,7 @@ export default Ember.Controller.extend({
|
||||
|
||||
@computed('q')
|
||||
noSortQ(q) {
|
||||
if (q) {
|
||||
SortOrders.forEach((order) => {
|
||||
if (q.indexOf(order.term) > -1){
|
||||
q = q.replace(order.term, "");
|
||||
q = q.trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
q = this.cleanTerm(q);
|
||||
return escapeExpression(q);
|
||||
},
|
||||
|
||||
@ -88,17 +83,23 @@ export default Ember.Controller.extend({
|
||||
|
||||
setSearchTerm(term) {
|
||||
this._searchOnSortChange = false;
|
||||
term = this.cleanTerm(term);
|
||||
this._searchOnSortChange = true;
|
||||
this.set('searchTerm', term);
|
||||
},
|
||||
|
||||
cleanTerm(term) {
|
||||
if (term) {
|
||||
SortOrders.forEach(order => {
|
||||
if (term.indexOf(order.term) > -1){
|
||||
let matches = term.match(new RegExp(`${order.term}\\b`));
|
||||
if (matches) {
|
||||
this.set('sortOrder', order.id);
|
||||
term = term.replace(order.term, "");
|
||||
term = term.replace(new RegExp(`${order.term}\\b`, 'g'), "");
|
||||
term = term.trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
this._searchOnSortChange = true;
|
||||
this.set('searchTerm', term);
|
||||
return term;
|
||||
},
|
||||
|
||||
@observes('sortOrder')
|
||||
|
||||
@ -1,23 +1,26 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
emailSent: false,
|
||||
|
||||
onShow() {
|
||||
this.set("emailSent", false);
|
||||
},
|
||||
|
||||
actions: {
|
||||
sendActivationEmail() {
|
||||
ajax('/users/action/send_activation_email', {
|
||||
ajax(userPath('action/send_activation_email'), {
|
||||
data: { username: this.get('username') },
|
||||
type: 'POST'
|
||||
}).then(() => {
|
||||
this.set('emailSent', true);
|
||||
const modal = this.showModal('activation-resent', {title: 'log_in'});
|
||||
modal.set('currentEmail', this.get('currentEmail'));
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
editActivationEmail() {
|
||||
const modal = this.showModal('activation-edit', {title: 'login.change_email'});
|
||||
|
||||
const currentEmail = this.get('currentEmail');
|
||||
modal.set('currentEmail', currentEmail);
|
||||
modal.set('newEmail', currentEmail);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -3,6 +3,7 @@ import getUrl from 'discourse-common/lib/get-url';
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import PasswordValidation from "discourse/mixins/password-validation";
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export default Ember.Controller.extend(PasswordValidation, {
|
||||
isDeveloper: Ember.computed.alias('model.is_developer'),
|
||||
@ -27,7 +28,7 @@ export default Ember.Controller.extend(PasswordValidation, {
|
||||
actions: {
|
||||
submit() {
|
||||
ajax({
|
||||
url: `/users/password-reset/${this.get('model.token')}.json`,
|
||||
url: userPath(`password-reset/${this.get('model.token')}.json`),
|
||||
type: 'PUT',
|
||||
data: {
|
||||
password: this.get('accountPassword')
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { setting, propertyEqual } from 'discourse/lib/computed';
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
taken: false,
|
||||
@ -48,7 +49,7 @@ export default Ember.Controller.extend({
|
||||
if (result) {
|
||||
this.set('saving', true);
|
||||
this.get('content').changeUsername(this.get('newUsername')).then(() => {
|
||||
DiscourseURL.redirectTo("/users/" + this.get('newUsername').toLowerCase() + "/preferences");
|
||||
DiscourseURL.redirectTo(userPath(this.get('newUsername').toLowerCase() + "/preferences"));
|
||||
})
|
||||
.catch(() => this.set('error', true))
|
||||
.finally(() => this.set('saving', false));
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
application: Ember.inject.controller(),
|
||||
@ -18,7 +19,7 @@ export default Ember.Controller.extend({
|
||||
markFaqRead() {
|
||||
const currentUser = this.currentUser;
|
||||
if (currentUser) {
|
||||
ajax("/users/read-faq", { method: "POST" }).then(() => {
|
||||
ajax(userPath("read-faq"), { method: "POST" }).then(() => {
|
||||
currentUser.set('read_faq', true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import Post from 'discourse/models/post';
|
||||
import debounce from 'discourse/lib/debounce';
|
||||
import isElementInViewport from "discourse/lib/is-element-in-viewport";
|
||||
import QuoteState from 'discourse/lib/quote-state';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||
composer: Ember.inject.controller(),
|
||||
@ -126,7 +127,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||
showCategoryChooser: Ember.computed.not("model.isPrivateMessage"),
|
||||
|
||||
gotoInbox(name) {
|
||||
var url = '/users/' + this.get('currentUser.username_lower') + '/messages';
|
||||
let url = userPath(this.get('currentUser.username_lower') + '/messages');
|
||||
if (name) {
|
||||
url = url + '/group/' + name;
|
||||
}
|
||||
@ -160,10 +161,6 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||
return this.send(name, model);
|
||||
},
|
||||
|
||||
openAutoClose() {
|
||||
this.send('showAutoClose');
|
||||
},
|
||||
|
||||
openFeatureTopic() {
|
||||
this.send('showFeatureTopic');
|
||||
},
|
||||
@ -591,7 +588,11 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||
},
|
||||
|
||||
toggleClosed() {
|
||||
this.get('content').toggleStatus('closed');
|
||||
const topic = this.get('content');
|
||||
|
||||
this.get('content').toggleStatus('closed').then(result => {
|
||||
topic.set('topic_status_update', result.topic_status_update);
|
||||
});
|
||||
},
|
||||
|
||||
recoverTopic() {
|
||||
@ -867,7 +868,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||
|
||||
const refresh = (args) => this.appEvents.trigger('post-stream:refresh', args);
|
||||
|
||||
this.messageBus.subscribe("/topic/" + this.get('model.id'), data => {
|
||||
this.messageBus.subscribe(`/topic/${this.get('model.id')}`, data => {
|
||||
const topic = this.get('model');
|
||||
|
||||
if (data.notification_level_change) {
|
||||
@ -877,9 +878,24 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||
}
|
||||
|
||||
const postStream = this.get('model.postStream');
|
||||
|
||||
if (data.reload_topic) {
|
||||
topic.reload().then(() => {
|
||||
this.send('postChangedRoute', topic.get('post_number') || 1);
|
||||
this.appEvents.trigger('header:update-topic', topic);
|
||||
if (data.refresh_stream) postStream.refresh();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case "acted":
|
||||
postStream.triggerChangedPost(data.id, data.updated_at).then(() => refresh({ id: data.id, refreshLikes: true }));
|
||||
postStream.triggerChangedPost(
|
||||
data.id,
|
||||
data.updated_at,
|
||||
{ preserveCooked: true }
|
||||
).then(() => refresh({ id: data.id, refreshLikes: true }));
|
||||
break;
|
||||
case "revised":
|
||||
case "rebaked": {
|
||||
@ -914,27 +930,20 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||
}
|
||||
}
|
||||
|
||||
if (data.reload_topic) {
|
||||
topic.reload().then(() => {
|
||||
this.send('postChangedRoute', topic.get('post_number') || 1);
|
||||
this.appEvents.trigger('header:update-topic', topic);
|
||||
});
|
||||
} else {
|
||||
if (topic.get('isPrivateMessage') &&
|
||||
this.currentUser &&
|
||||
this.currentUser.get('id') !== data.user_id &&
|
||||
data.type === 'created') {
|
||||
if (topic.get('isPrivateMessage') &&
|
||||
this.currentUser &&
|
||||
this.currentUser.get('id') !== data.user_id &&
|
||||
data.type === 'created') {
|
||||
|
||||
const postNumber = data.post_number;
|
||||
const notInPostStream = topic.get('highest_post_number') <= postNumber;
|
||||
const postNumberDifference = postNumber - topic.get('currentPost');
|
||||
const postNumber = data.post_number;
|
||||
const notInPostStream = topic.get('highest_post_number') <= postNumber;
|
||||
const postNumberDifference = postNumber - topic.get('currentPost');
|
||||
|
||||
if (notInPostStream &&
|
||||
postNumberDifference > 0 &&
|
||||
postNumberDifference < 7) {
|
||||
if (notInPostStream &&
|
||||
postNumberDifference > 0 &&
|
||||
postNumberDifference < 7) {
|
||||
|
||||
this._scrollToPost(data.post_number);
|
||||
}
|
||||
this._scrollToPost(data.post_number);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -12,12 +12,14 @@ export default {
|
||||
DiscourseURL.rewrite(/^\/category\//, "/c/");
|
||||
DiscourseURL.rewrite(/^\/group\//, "/groups/");
|
||||
DiscourseURL.rewrite(/\/private-messages\/$/, "/messages/");
|
||||
DiscourseURL.rewrite(/^\/users$/, "/u");
|
||||
DiscourseURL.rewrite(/^\/users\//, "/u/");
|
||||
|
||||
if (currentUser) {
|
||||
const username = currentUser.get('username');
|
||||
DiscourseURL.rewrite(new RegExp(`^/users/${username}/?$`, "i"), `/users/${username}/activity`);
|
||||
DiscourseURL.rewrite(new RegExp(`^/u/${username}/?$`, "i"), `/u/${username}/activity`);
|
||||
}
|
||||
|
||||
DiscourseURL.rewrite(/^\/users\/([^\/]+)\/?$/, "/users/$1/activity");
|
||||
DiscourseURL.rewrite(/^\/u\/([^\/]+)\/?$/, "/u/$1/summary");
|
||||
}
|
||||
};
|
||||
|
||||
@ -66,12 +66,10 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
getURL() {
|
||||
const location = get(this, 'location');
|
||||
let url = location.pathname;
|
||||
|
||||
url = url.replace(Discourse.BaseUri, '');
|
||||
|
||||
const search = location.search || '';
|
||||
url += search;
|
||||
|
||||
return url;
|
||||
},
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
function replaceSpan($e, username, opts) {
|
||||
let extra = "";
|
||||
@ -15,7 +16,7 @@ function replaceSpan($e, username, opts) {
|
||||
extra = `data-name='${username}'`;
|
||||
extraClass = "cannot-see";
|
||||
}
|
||||
$e.replaceWith(`<a href='${Discourse.getURL("/users/") + username.toLowerCase()}' class='mention ${extraClass}' ${extra}>@${username}</a>`);
|
||||
$e.replaceWith(`<a href='${userPath(username.toLowerCase())}' class='mention ${extraClass}' ${extra}>@${username}</a>`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +55,7 @@ export function linkSeenMentions($elem, siteSettings) {
|
||||
// 'Create a New Topic' scenario is not supported (per conversation with codinghorror)
|
||||
// https://meta.discourse.org/t/taking-another-1-7-release-task/51986/7
|
||||
export function fetchUnseenMentions(usernames, topic_id) {
|
||||
return ajax("/users/is_local_username", { data: { usernames, topic_id } }).then(r => {
|
||||
return ajax(userPath("is_local_username"), { data: { usernames, topic_id } }).then(r => {
|
||||
r.valid.forEach(v => found[v] = true);
|
||||
r.valid_groups.forEach(vg => foundGroups[vg] = true);
|
||||
r.mentionable_groups.forEach(mg => mentionableGroups[mg.name] = mg);
|
||||
|
||||
@ -55,6 +55,8 @@ export default class LockOn {
|
||||
|
||||
const interval = setInterval(() => {
|
||||
let top = this.elementTop();
|
||||
if (top < 0) { top = 0; }
|
||||
|
||||
const scrollTop = $(window).scrollTop();
|
||||
|
||||
if (typeof(top) === "undefined" || isNaN(top)) {
|
||||
|
||||
@ -18,9 +18,11 @@ import { addTagsHtmlCallback } from 'discourse/lib/render-tags';
|
||||
import { addUserMenuGlyph } from 'discourse/widgets/user-menu';
|
||||
import { addPostClassesCallback } from 'discourse/widgets/post';
|
||||
import { addPostTransformCallback } from 'discourse/widgets/post-stream';
|
||||
import { attachAdditionalPanel } from 'discourse/widgets/header';
|
||||
|
||||
|
||||
// If you add any methods to the API ensure you bump up this number
|
||||
const PLUGIN_API_VERSION = '0.8.5';
|
||||
const PLUGIN_API_VERSION = '0.8.6';
|
||||
|
||||
class PluginApi {
|
||||
constructor(version, container) {
|
||||
@ -333,6 +335,26 @@ class PluginApi {
|
||||
return addFlagProperty(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a panel to the header
|
||||
*
|
||||
* takes a widget name, a value to toggle on, and a function which returns the attrs for the widget
|
||||
* Example:
|
||||
* ```javascript
|
||||
* api.addHeaderPanel('widget-name', 'widgetVisible', function(attrs, state) {
|
||||
* return { name: attrs.name, description: state.description };
|
||||
* });
|
||||
* ```
|
||||
* 'toggle' is an attribute on the state of the header widget,
|
||||
*
|
||||
* 'transformAttrs' is a function which is passed the current attrs and state of the widget,
|
||||
* and returns a hash of values to pass to attach
|
||||
*
|
||||
**/
|
||||
addHeaderPanel(name, toggle, transformAttrs) {
|
||||
attachAdditionalPanel(name, toggle, transformAttrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a pluralization to the store
|
||||
*
|
||||
|
||||
@ -5,6 +5,7 @@ import { SEPARATOR } from 'discourse/lib/category-hashtags';
|
||||
import Category from 'discourse/models/category';
|
||||
import { search as searchCategoryTag } from 'discourse/lib/category-tag-search';
|
||||
import userSearch from 'discourse/lib/user-search';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export function translateResults(results, opts) {
|
||||
|
||||
@ -29,7 +30,7 @@ export function translateResults(results, opts) {
|
||||
|
||||
results.posts = results.posts.map(post => {
|
||||
if (post.username) {
|
||||
post.userPath = Discourse.getURL(`/users/${post.username.toLowerCase()}`);
|
||||
post.userPath = userPath(post.username.toLowerCase());
|
||||
}
|
||||
post = Post.create(post);
|
||||
post.set('topic', topicMap[post.topic_id]);
|
||||
|
||||
@ -11,17 +11,23 @@ export default function(name, opts) {
|
||||
|
||||
const controllerName = opts.admin ? `modals/${name}` : name;
|
||||
|
||||
const controller = container.lookup('controller:' + controllerName);
|
||||
let controller = container.lookup('controller:' + controllerName);
|
||||
const templateName = opts.templateName || Ember.String.dasherize(name);
|
||||
|
||||
const renderArgs = { into: 'modal', outlet: 'modalBody'};
|
||||
if (controller) { renderArgs.controller = controllerName; }
|
||||
if (controller) {
|
||||
renderArgs.controller = controllerName;
|
||||
} else {
|
||||
// use a basic controller
|
||||
renderArgs.controller = 'basic-modal-body';
|
||||
controller = container.lookup(`controller:${renderArgs.controller}`);
|
||||
}
|
||||
|
||||
|
||||
if (opts.addModalBodyView) {
|
||||
renderArgs.view = 'modal-body';
|
||||
}
|
||||
|
||||
|
||||
const modalName = `modal/${templateName}`;
|
||||
const fullName = opts.admin ? `admin/templates/${modalName}` : modalName;
|
||||
route.render(fullName, renderArgs);
|
||||
@ -29,13 +35,11 @@ export default function(name, opts) {
|
||||
modalController.set('title', I18n.t(opts.title));
|
||||
}
|
||||
|
||||
if (controller) {
|
||||
controller.set('modal', modalController);
|
||||
const model = opts.model;
|
||||
if (model) { controller.set('model', model); }
|
||||
if (controller.onShow) { controller.onShow(); }
|
||||
controller.set('flashMessage', null);
|
||||
}
|
||||
controller.set('modal', modalController);
|
||||
const model = opts.model;
|
||||
if (model) { controller.set('model', model); }
|
||||
if (controller.onShow) { controller.onShow(); }
|
||||
controller.set('flashMessage', null);
|
||||
|
||||
return controller;
|
||||
};
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
function actionDescription(action, acted, count) {
|
||||
if (acted) {
|
||||
if (count <= 1) {
|
||||
@ -39,7 +41,7 @@ export function transformBasicPost(post) {
|
||||
via_email: post.via_email,
|
||||
isAutoGenerated: post.is_auto_generated,
|
||||
user_id: post.user_id,
|
||||
usernameUrl: Discourse.getURL(`/users/${post.username}`),
|
||||
usernameUrl: userPath(post.username),
|
||||
username: post.username,
|
||||
avatar_template: post.avatar_template,
|
||||
bookmarked: post.bookmarked,
|
||||
|
||||
@ -18,6 +18,27 @@ const SERVER_SIDE_ONLY = [
|
||||
/\.json$/,
|
||||
];
|
||||
|
||||
export function rewritePath(path) {
|
||||
const params = path.split("?");
|
||||
|
||||
let result = params[0];
|
||||
rewrites.forEach(rw => result = result.replace(rw.regexp, rw.replacement));
|
||||
|
||||
if (params.length > 1) {
|
||||
result += `?${params[1]}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function clearRewrites() {
|
||||
rewrites.length = 0;
|
||||
}
|
||||
|
||||
export function userPath(subPath) {
|
||||
return Discourse.getURL(subPath ? `/u/${subPath}` : '/u');
|
||||
}
|
||||
|
||||
let _jumpScheduled = false;
|
||||
export function jumpToElement(elementId) {
|
||||
if (_jumpScheduled || Ember.isEmpty(elementId)) { return; }
|
||||
@ -47,8 +68,8 @@ const DiscourseURL = Ember.Object.extend({
|
||||
opts = opts || {};
|
||||
const holderId = `#post_${postNumber}`;
|
||||
|
||||
_transitioning = true;
|
||||
Em.run.schedule('afterRender', () => {
|
||||
_transitioning = postNumber > 1;
|
||||
Ember.run.schedule('afterRender', () => {
|
||||
let elementId;
|
||||
let holder;
|
||||
|
||||
@ -87,6 +108,10 @@ const DiscourseURL = Ember.Object.extend({
|
||||
}
|
||||
|
||||
lockon.lock();
|
||||
if (lockon.elementTop() < 1) {
|
||||
_transitioning = false;
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@ -95,7 +120,6 @@ const DiscourseURL = Ember.Object.extend({
|
||||
if (window.history &&
|
||||
window.history.pushState &&
|
||||
window.history.replaceState &&
|
||||
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/) &&
|
||||
(window.location.pathname !== path)) {
|
||||
|
||||
// Always use replaceState in the next runloop to prevent weird routes changing
|
||||
@ -173,15 +197,14 @@ const DiscourseURL = Ember.Object.extend({
|
||||
if (path.indexOf('/my/') === 0) {
|
||||
const currentUser = Discourse.User.current();
|
||||
if (currentUser) {
|
||||
path = path.replace('/my/', '/users/' + currentUser.get('username_lower') + "/");
|
||||
path = path.replace('/my/', userPath(currentUser.get('username_lower') + "/"));
|
||||
} else {
|
||||
document.location.href = "/404";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rewrites.forEach(rw => path = path.replace(rw.regexp, rw.replacement));
|
||||
|
||||
path = rewritePath(path);
|
||||
if (this.navigatedToPost(oldPath, path, opts)) { return; }
|
||||
|
||||
if (oldPath === path) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { CANCELLED_STATUS } from 'discourse/lib/autocomplete';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
var cache = {},
|
||||
cacheTopicId,
|
||||
@ -14,7 +15,7 @@ function performSearch(term, topicId, includeGroups, includeMentionableGroups, a
|
||||
}
|
||||
|
||||
// need to be able to cancel this
|
||||
oldSearch = $.ajax(Discourse.getURL('/users/search/users'), {
|
||||
oldSearch = $.ajax(userPath('search/users'), {
|
||||
data: { term: term,
|
||||
topic_id: topicId,
|
||||
include_groups: includeGroups,
|
||||
|
||||
@ -65,10 +65,6 @@ export function postUrl(slug, topicId, postNumber) {
|
||||
return url;
|
||||
}
|
||||
|
||||
export function userUrl(username) {
|
||||
return Discourse.getURL("/users/" + username.toLowerCase());
|
||||
}
|
||||
|
||||
export function emailValid(email) {
|
||||
// see: http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
|
||||
const re = /^[a-zA-Z0-9!#$%&'*+\/=?\^_`{|}~\-]+(?:\.[a-zA-Z0-9!#$%&'\*+\/=?\^_`{|}~\-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?$/;
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { defaultHomepage } from 'discourse/lib/utilities';
|
||||
import { rewritePath } from 'discourse/lib/url';
|
||||
|
||||
const rootURL = Discourse.BaseUri;
|
||||
|
||||
const BareRouter = Ember.Router.extend({
|
||||
@ -6,6 +8,7 @@ const BareRouter = Ember.Router.extend({
|
||||
location: Ember.testing ? 'none': 'discourse-location',
|
||||
|
||||
handleURL(url) {
|
||||
url = rewritePath(url);
|
||||
const params = url.split('?');
|
||||
|
||||
if (params[0] === "/") {
|
||||
|
||||
@ -1,5 +1,17 @@
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
|
||||
export default Ember.Mixin.create({
|
||||
flash(text, messageClass) {
|
||||
this.appEvents.trigger('modal-body:flash', { text, messageClass });
|
||||
},
|
||||
|
||||
showModal(...args) {
|
||||
return showModal(...args);
|
||||
},
|
||||
|
||||
actions: {
|
||||
closeModal() {
|
||||
this.get('modal').send('closeModal');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -32,7 +32,7 @@ export default Ember.Mixin.create({
|
||||
topicTitle,
|
||||
topicBody,
|
||||
archetypeId: 'private_message',
|
||||
draftKey: 'new_private_message'
|
||||
draftKey: Composer.NEW_PRIVATE_MESSAGE_KEY
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ const CLOSED = 'closed',
|
||||
// The actions the composer can take
|
||||
CREATE_TOPIC = 'createTopic',
|
||||
PRIVATE_MESSAGE = 'privateMessage',
|
||||
NEW_PRIVATE_MESSAGE_KEY = 'new_private_message',
|
||||
REPLY = 'reply',
|
||||
EDIT = 'edit',
|
||||
REPLY_AS_NEW_TOPIC_KEY = "reply_as_new_topic",
|
||||
@ -815,6 +816,7 @@ Composer.reopenClass({
|
||||
EDIT,
|
||||
|
||||
// Draft key
|
||||
NEW_PRIVATE_MESSAGE_KEY,
|
||||
REPLY_AS_NEW_TOPIC_KEY,
|
||||
REPLY_AS_NEW_PRIVATE_MESSAGE_KEY
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
const Invite = Discourse.Model.extend({
|
||||
|
||||
@ -41,7 +42,7 @@ Invite.reopenClass({
|
||||
if (!Em.isNone(search)) { data.search = search; }
|
||||
data.offset = offset || 0;
|
||||
|
||||
return ajax("/users/" + user.get('username_lower') + "/invited.json", {data}).then(function (result) {
|
||||
return ajax(userPath(user.get('username_lower') + "/invited.json"), {data}).then(function (result) {
|
||||
result.invites = result.invites.map(function (i) {
|
||||
return Invite.create(i);
|
||||
});
|
||||
@ -52,7 +53,7 @@ Invite.reopenClass({
|
||||
|
||||
findInvitedCount(user) {
|
||||
if (!user) { return Em.RSVP.resolve(); }
|
||||
return ajax("/users/" + user.get('username_lower') + "/invited_count.json").then(result => Em.Object.create(result.counts));
|
||||
return ajax(userPath(user.get('username_lower') + "/invited_count.json")).then(result => Em.Object.create(result.counts));
|
||||
},
|
||||
|
||||
reinviteAll() {
|
||||
|
||||
@ -15,6 +15,7 @@ export default RestModel.extend({
|
||||
loadingAbove: null,
|
||||
loadingBelow: null,
|
||||
loadingFilter: null,
|
||||
loadingNearPost: null,
|
||||
stagingPost: null,
|
||||
postsWithPlaceholders: null,
|
||||
timelineLookup: null,
|
||||
@ -206,6 +207,7 @@ export default RestModel.extend({
|
||||
|
||||
// TODO: if we have all the posts in the filter, don't go to the server for them.
|
||||
this.set('loadingFilter', true);
|
||||
this.set('loadingNearPost', opts.nearPost);
|
||||
|
||||
opts = _.merge(opts, this.get('streamFilters'));
|
||||
|
||||
@ -216,6 +218,8 @@ export default RestModel.extend({
|
||||
}).catch(result => {
|
||||
this.errorLoading(result);
|
||||
throw result;
|
||||
}).finally(() => {
|
||||
this.set('loadingNearPost', null);
|
||||
});
|
||||
},
|
||||
|
||||
@ -540,7 +544,9 @@ export default RestModel.extend({
|
||||
return Ember.RSVP.Promise.resolve();
|
||||
},
|
||||
|
||||
triggerChangedPost(postId, updatedAt) {
|
||||
triggerChangedPost(postId, updatedAt, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
const resolved = Ember.RSVP.Promise.resolve();
|
||||
if (!postId) { return resolved; }
|
||||
|
||||
@ -548,7 +554,13 @@ export default RestModel.extend({
|
||||
if (existing && existing.updated_at !== updatedAt) {
|
||||
const url = "/posts/" + postId;
|
||||
const store = this.store;
|
||||
return ajax(url).then(p => this.storePost(store.createRecord('post', p)));
|
||||
return ajax(url).then(p => {
|
||||
if (opts.preserveCooked) {
|
||||
p.cooked = existing.get('cooked');
|
||||
}
|
||||
|
||||
this.storePost(store.createRecord('post', p));
|
||||
});
|
||||
}
|
||||
return resolved;
|
||||
},
|
||||
|
||||
@ -2,11 +2,12 @@ import { ajax } from 'discourse/lib/ajax';
|
||||
import RestModel from 'discourse/models/rest';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import ActionSummary from 'discourse/models/action-summary';
|
||||
import { url, propertyEqual } from 'discourse/lib/computed';
|
||||
import { propertyEqual } from 'discourse/lib/computed';
|
||||
import Quote from 'discourse/lib/quote';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { postUrl } from 'discourse/lib/utilities';
|
||||
import { cook } from 'discourse/lib/text';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
const Post = RestModel.extend({
|
||||
|
||||
@ -60,7 +61,10 @@ const Post = RestModel.extend({
|
||||
return postNumber === 1 ? baseUrl + "/1" : baseUrl;
|
||||
},
|
||||
|
||||
usernameUrl: url('username', '/users/%@'),
|
||||
@computed('username')
|
||||
usernameUrl(username) {
|
||||
return userPath(username);
|
||||
},
|
||||
|
||||
topicOwner: propertyEqual('topic.details.created_by.id', 'user_id'),
|
||||
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import RestModel from 'discourse/models/rest';
|
||||
|
||||
const TopicStatusUpdate = RestModel.extend({});
|
||||
|
||||
TopicStatusUpdate.reopenClass({
|
||||
updateStatus(topicId, time, basedOnLastPost, statusType, categoryId) {
|
||||
let data = {
|
||||
time: time,
|
||||
timezone_offset: (new Date().getTimezoneOffset()),
|
||||
status_type: statusType
|
||||
};
|
||||
|
||||
if (basedOnLastPost) data.based_on_last_post = basedOnLastPost;
|
||||
if (categoryId) data.category_id = categoryId;
|
||||
|
||||
return ajax({
|
||||
url: `/t/${topicId}/status_update`,
|
||||
type: 'POST',
|
||||
data
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default TopicStatusUpdate;
|
||||
@ -9,6 +9,7 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import { censor } from 'pretty-text/censored-words';
|
||||
import { emojiUnescape } from 'discourse/lib/text';
|
||||
import PreloadStore from 'preload-store';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export function loadTopicView(topic, args) {
|
||||
const topicId = topic.get('id');
|
||||
@ -182,9 +183,10 @@ const Topic = RestModel.extend({
|
||||
return this.urlForPostNumber(1) + (this.get('has_summary') ? "?filter=summary" : "");
|
||||
}.property('url'),
|
||||
|
||||
lastPosterUrl: function() {
|
||||
return Discourse.getURL("/users/") + this.get("last_poster.username");
|
||||
}.property('last_poster'),
|
||||
@computed('last_poster.username')
|
||||
lastPosterUrl(username) {
|
||||
return userPath(username);
|
||||
},
|
||||
|
||||
// The amount of new posts to display. It might be different than what the server
|
||||
// tells us if we are still asynchronously flushing our "recently read" data.
|
||||
@ -221,16 +223,12 @@ const Topic = RestModel.extend({
|
||||
|
||||
toggleStatus(property) {
|
||||
this.toggleProperty(property);
|
||||
this.saveStatus(property, !!this.get(property));
|
||||
return this.saveStatus(property, !!this.get(property));
|
||||
},
|
||||
|
||||
saveStatus(property, value, until) {
|
||||
if (property === 'closed') {
|
||||
this.incrementProperty('posts_count');
|
||||
|
||||
if (value === true) {
|
||||
this.set('details.auto_close_at', null);
|
||||
}
|
||||
}
|
||||
return ajax(this.get('url') + "/status", {
|
||||
type: 'PUT',
|
||||
@ -378,9 +376,8 @@ const Topic = RestModel.extend({
|
||||
},
|
||||
|
||||
reload() {
|
||||
const self = this;
|
||||
return ajax('/t/' + this.get('id'), { type: 'GET' }).then(function(topic_json) {
|
||||
self.updateFromJson(topic_json);
|
||||
return ajax(`/t/${this.get('id')}`, { type: 'GET' }).then(topic_json => {
|
||||
this.updateFromJson(topic_json);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import RestModel from 'discourse/models/rest';
|
||||
import { url } from 'discourse/lib/computed';
|
||||
import { on } from 'ember-addons/ember-computed-decorators';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import UserActionGroup from 'discourse/models/user-action-group';
|
||||
import { postUrl } from 'discourse/lib/utilities';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
const UserActionTypes = {
|
||||
likes_given: 1,
|
||||
@ -79,14 +79,21 @@ const UserAction = RestModel.extend({
|
||||
presentName: Ember.computed.or('name', 'username'),
|
||||
targetDisplayName: Ember.computed.or('target_name', 'target_username'),
|
||||
actingDisplayName: Ember.computed.or('acting_name', 'acting_username'),
|
||||
targetUserUrl: url('target_username', '/users/%@'),
|
||||
|
||||
@computed('target_username')
|
||||
targetUserUrl(username) {
|
||||
return userPath(username);
|
||||
},
|
||||
|
||||
@computed("username")
|
||||
usernameLower(username) {
|
||||
return username.toLowerCase();
|
||||
},
|
||||
|
||||
userUrl: url('usernameLower', '/users/%@'),
|
||||
@computed('usernameLower')
|
||||
userUrl(usernameLower) {
|
||||
return userPath(usernameLower);
|
||||
},
|
||||
|
||||
@computed()
|
||||
postUrl() {
|
||||
|
||||
@ -15,6 +15,7 @@ import Topic from 'discourse/models/topic';
|
||||
import { emojiUnescape } from 'discourse/lib/text';
|
||||
import PreloadStore from 'preload-store';
|
||||
import { defaultHomepage } from 'discourse/lib/utilities';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
const User = RestModel.extend({
|
||||
|
||||
@ -71,7 +72,7 @@ const User = RestModel.extend({
|
||||
@computed()
|
||||
path() {
|
||||
// no need to observe, requires a hard refresh to update
|
||||
return Discourse.getURL(`/users/${this.get('username_lower')}`);
|
||||
return userPath(this.get('username_lower'));
|
||||
},
|
||||
|
||||
@computed()
|
||||
@ -124,11 +125,10 @@ const User = RestModel.extend({
|
||||
|
||||
// directly targetted so go to inbox
|
||||
if (!groups || (allowedUsers && allowedUsers.findBy("id", userId))) {
|
||||
return Discourse.getURL(`/users/${username}/messages`);
|
||||
return userPath(`${username}/messages`);
|
||||
} else {
|
||||
if (groups && groups[0])
|
||||
{
|
||||
return Discourse.getURL(`/users/${username}/messages/group/${groups[0].name}`);
|
||||
if (groups && groups[0]) {
|
||||
return userPath(`${username}/messages/group/${groups[0].name}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,6 +146,11 @@ const User = RestModel.extend({
|
||||
return defaultHomepage() === "latest" ? Discourse.getURL('/?state=watching') : Discourse.getURL('/latest?state=watching');
|
||||
},
|
||||
|
||||
@computed()
|
||||
trackingTopicsPath() {
|
||||
return defaultHomepage() === "latest" ? Discourse.getURL('/?state=tracking') : Discourse.getURL('/latest?state=tracking');
|
||||
},
|
||||
|
||||
@computed("username")
|
||||
username_lower(username) {
|
||||
return username.toLowerCase();
|
||||
@ -179,14 +184,14 @@ const User = RestModel.extend({
|
||||
},
|
||||
|
||||
changeUsername(new_username) {
|
||||
return ajax(`/users/${this.get('username_lower')}/preferences/username`, {
|
||||
return ajax(userPath(`${this.get('username_lower')}/preferences/username`), {
|
||||
type: 'PUT',
|
||||
data: { new_username }
|
||||
});
|
||||
},
|
||||
|
||||
changeEmail(email) {
|
||||
return ajax(`/users/${this.get('username_lower')}/preferences/email`, {
|
||||
return ajax(userPath(`${this.get('username_lower')}/preferences/email`), {
|
||||
type: 'PUT',
|
||||
data: { email }
|
||||
});
|
||||
@ -254,7 +259,7 @@ const User = RestModel.extend({
|
||||
|
||||
// TODO: We can remove this when migrated fully to rest model.
|
||||
this.set('isSaving', true);
|
||||
return ajax(`/users/${this.get('username_lower')}.json`, {
|
||||
return ajax(userPath(`${this.get('username_lower')}.json`), {
|
||||
data: data,
|
||||
type: 'PUT'
|
||||
}).then(result => {
|
||||
@ -330,7 +335,7 @@ const User = RestModel.extend({
|
||||
const user = this;
|
||||
|
||||
return PreloadStore.getAndRemove(`user_${user.get('username')}`, () => {
|
||||
return ajax(`/users/${user.get('username')}.json`, { data: options });
|
||||
return ajax(userPath(`${user.get('username')}.json`), { data: options });
|
||||
}).then(json => {
|
||||
|
||||
if (!Em.isEmpty(json.user.stats)) {
|
||||
@ -375,13 +380,13 @@ const User = RestModel.extend({
|
||||
|
||||
findStaffInfo() {
|
||||
if (!Discourse.User.currentProp("staff")) { return Ember.RSVP.resolve(null); }
|
||||
return ajax(`/users/${this.get("username_lower")}/staff-info.json`).then(info => {
|
||||
return ajax(userPath(`${this.get("username_lower")}/staff-info.json`)).then(info => {
|
||||
this.setProperties(info);
|
||||
});
|
||||
},
|
||||
|
||||
pickAvatar(upload_id, type, avatar_template) {
|
||||
return ajax(`/users/${this.get("username_lower")}/preferences/avatar/pick`, {
|
||||
return ajax(userPath(`${this.get("username_lower")}/preferences/avatar/pick`), {
|
||||
type: 'PUT',
|
||||
data: { upload_id, type }
|
||||
}).then(() => this.setProperties({
|
||||
@ -437,7 +442,7 @@ const User = RestModel.extend({
|
||||
|
||||
"delete": function() {
|
||||
if (this.get('can_delete_account')) {
|
||||
return ajax("/users/" + this.get('username'), {
|
||||
return ajax(userPath(this.get('username')), {
|
||||
type: 'DELETE',
|
||||
data: {context: window.location.pathname}
|
||||
});
|
||||
@ -448,14 +453,14 @@ const User = RestModel.extend({
|
||||
|
||||
dismissBanner(bannerKey) {
|
||||
this.set("dismissed_banner_key", bannerKey);
|
||||
ajax(`/users/${this.get('username')}`, {
|
||||
ajax(userPath(this.get('username')), {
|
||||
type: 'PUT',
|
||||
data: { dismissed_banner_key: bannerKey }
|
||||
});
|
||||
},
|
||||
|
||||
checkEmail() {
|
||||
return ajax(`/users/${this.get("username_lower")}/emails.json`, {
|
||||
return ajax(userPath(`${this.get("username_lower")}/emails.json`), {
|
||||
data: { context: window.location.pathname }
|
||||
}).then(result => {
|
||||
if (result) {
|
||||
@ -468,7 +473,7 @@ const User = RestModel.extend({
|
||||
},
|
||||
|
||||
summary() {
|
||||
return ajax(`/users/${this.get("username_lower")}/summary.json`)
|
||||
return ajax(userPath(`${this.get("username_lower")}/summary.json`))
|
||||
.then(json => {
|
||||
const summary = json["user_summary"];
|
||||
const topicMap = {};
|
||||
@ -526,7 +531,7 @@ User.reopenClass(Singleton, {
|
||||
},
|
||||
|
||||
checkUsername(username, email, for_user_id) {
|
||||
return ajax('/users/check_username', {
|
||||
return ajax(userPath('check_username'), {
|
||||
data: { username, email, for_user_id }
|
||||
});
|
||||
},
|
||||
@ -557,7 +562,7 @@ User.reopenClass(Singleton, {
|
||||
},
|
||||
|
||||
createAccount(attrs) {
|
||||
return ajax("/users", {
|
||||
return ajax(userPath(), {
|
||||
data: {
|
||||
name: attrs.accountName,
|
||||
email: attrs.accountEmail,
|
||||
|
||||
@ -62,9 +62,9 @@ export default function() {
|
||||
});
|
||||
|
||||
// User routes
|
||||
this.route('users', { resetNamespace: true });
|
||||
this.route('password-reset', { path: '/users/password-reset/:token' });
|
||||
this.route('user', { path: '/users/:username', resetNamespace: true }, function() {
|
||||
this.route('users', { resetNamespace: true, path: '/u' });
|
||||
this.route('password-reset', { path: '/u/password-reset/:token' });
|
||||
this.route('user', { path: '/u/:username', resetNamespace: true }, function() {
|
||||
this.route('summary');
|
||||
this.route('userActivity', { path: '/activity', resetNamespace: true }, function() {
|
||||
this.route('topics');
|
||||
|
||||
@ -7,6 +7,7 @@ import Category from 'discourse/models/category';
|
||||
import mobile from 'discourse/lib/mobile';
|
||||
import { findAll } from 'discourse/models/login-method';
|
||||
import { getOwner } from 'discourse-common/lib/get-owner';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
function unlessReadOnly(method, message) {
|
||||
return function() {
|
||||
@ -23,7 +24,7 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
|
||||
|
||||
actions: {
|
||||
toggleAnonymous() {
|
||||
ajax("/users/toggle-anon", {method: 'POST'}).then(() => {
|
||||
ajax(userPath("toggle-anon"), {method: 'POST'}).then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import PreloadStore from 'preload-store';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
titleToken() {
|
||||
@ -15,7 +16,7 @@ export default Discourse.Route.extend({
|
||||
afterModel(model) {
|
||||
// confirm token here so email clients who crawl URLs don't invalidate the link
|
||||
if (model) {
|
||||
return ajax({ url: `/users/confirm-email-token/${model.token}.json`, dataType: 'json' });
|
||||
return ajax({ url: userPath(`confirm-email-token/${model.token}.json`), dataType: 'json' });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -50,9 +50,11 @@ const TopicRoute = Discourse.Route.extend({
|
||||
this.controllerFor('flag').setProperties({ selected: null, flagTopic: true });
|
||||
},
|
||||
|
||||
showAutoClose() {
|
||||
showModal('edit-topic-auto-close', { model: this.modelFor('topic') });
|
||||
this.controllerFor('modal').set('modalClass', 'edit-auto-close-modal');
|
||||
showTopicStatusUpdate() {
|
||||
const model = this.modelFor('topic');
|
||||
model.set('topic_status_update', Ember.Object.create(model.get('topic_status_update')));
|
||||
showModal('edit-topic-status-update', { model });
|
||||
this.controllerFor('modal').set('modalClass', 'topic-close-modal');
|
||||
},
|
||||
|
||||
showChangeTimestamp() {
|
||||
|
||||
@ -83,14 +83,14 @@ export default Discourse.Route.extend({
|
||||
activate() {
|
||||
this._super();
|
||||
const user = this.modelFor('user');
|
||||
this.messageBus.subscribe("/users/" + user.get('username_lower'), function(data) {
|
||||
this.messageBus.subscribe("/u/" + user.get('username_lower'), function(data) {
|
||||
user.loadUserAction(data);
|
||||
});
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this._super();
|
||||
this.messageBus.unsubscribe("/users/" + this.modelFor('user').get('username_lower'));
|
||||
this.messageBus.unsubscribe("/u/" + this.modelFor('user').get('username_lower'));
|
||||
|
||||
// Remove the search context
|
||||
this.searchService.set('searchContext', null);
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
<div class="auto-close-fields">
|
||||
<div>
|
||||
<label>
|
||||
{{fa-icon "clock-o"}}
|
||||
{{i18n 'composer.auto_close.label'}}
|
||||
{{text-field value=autoCloseTime}}
|
||||
{{autoCloseUnits}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="examples">
|
||||
{{autoCloseExamples}}
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
{{input type="checkbox" checked=autoCloseBasedOnLastPost}}
|
||||
{{i18n 'composer.auto_close.based_on_last_post'}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,24 @@
|
||||
<div class="auto-update-input">
|
||||
<div class="control-group">
|
||||
<label>
|
||||
{{i18n inputLabelKey}}
|
||||
{{text-field value=input}}
|
||||
{{i18n inputUnitsKey}}
|
||||
</label>
|
||||
|
||||
{{#if inputExamplesKey}}
|
||||
<div class="examples">
|
||||
{{i18n inputExamplesKey}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#unless hideBasedOnLastPost}}
|
||||
<div class="control-group">
|
||||
<label>
|
||||
{{input type="checkbox" checked=basedOnLastPost}}
|
||||
{{i18n 'topic.auto_close.based_on_last_post'}}
|
||||
</label>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
@ -1,8 +1,10 @@
|
||||
<section class='field'>
|
||||
{{auto-close-form autoCloseTime=category.auto_close_hours
|
||||
autoCloseBasedOnLastPost=category.auto_close_based_on_last_post
|
||||
autoCloseExamples=""
|
||||
limited="true" }}
|
||||
{{auto-update-input
|
||||
inputLabelKey='topic.auto_close.label'
|
||||
input=category.auto_close_hours
|
||||
basedOnLastPost=category.auto_close_based_on_last_post
|
||||
inputExamplesKey=''
|
||||
limited=true}}
|
||||
</section>
|
||||
|
||||
<section class='field'>
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
<div class="modal-footer">
|
||||
<button class='btn btn-primary' {{action closeModal}}>{{i18n 'close'}}</button>
|
||||
</div>
|
||||
@ -8,7 +8,7 @@
|
||||
toggleClosed=toggleClosed
|
||||
toggleArchived=toggleArchived
|
||||
toggleVisibility=toggleVisibility
|
||||
showAutoClose=showAutoClose
|
||||
showTopicStatusUpdate=showTopicStatusUpdate
|
||||
showFeatureTopic=showFeatureTopic
|
||||
showChangeTimestamp=showChangeTimestamp
|
||||
convertToPublicTopic=convertToPublicTopic
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
<button title="{{i18n 'topics.bulk.dismiss_tooltip'}}" id='dismiss-topics' class='btn dismiss-read' {{action "dismissReadPosts"}}>{{i18n 'topics.bulk.dismiss_button'}}</button>
|
||||
{{/if}}
|
||||
{{#if showResetNew}}
|
||||
<button id='dismiss-new' class='btn dismiss-read' {{action "resetNew"}}>{{i18n 'topics.bulk.dismiss_new'}}</button>
|
||||
<button id='dismiss-new' class='btn dismiss-read' {{action "resetNew"}}><i class="fa fa-check"></i> {{i18n 'topics.bulk.dismiss_new'}}</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if latest}}
|
||||
|
||||
@ -3,10 +3,13 @@
|
||||
<div class="modal-middle-container">
|
||||
<div class="modal-inner-container">
|
||||
<div class="modal-header">
|
||||
<a class="close" {{action "closeModal"}}>{{fa-icon "times"}}</a>
|
||||
<h3>{{title}}</h3>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class='modal-close'>
|
||||
<a class="close" {{action "closeModal"}}>{{fa-icon "times"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='modal-alert'></div>
|
||||
{{outlet "modalBody"}}
|
||||
{{#each errors as |error|}}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
{{#d-modal-body}}
|
||||
<p>{{i18n "login.provide_new_email"}}</p>
|
||||
{{input value=newEmail class="activate-new-email"}}
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{d-button action="changeEmail"
|
||||
label="login.submit_new_email"
|
||||
disabled=submitDisabled
|
||||
class="btn-primary"}}
|
||||
{{d-button action="closeModal" label="close"}}
|
||||
</div>
|
||||
@ -0,0 +1,5 @@
|
||||
{{#d-modal-body}}
|
||||
{{{i18n 'login.sent_activation_email_again' currentEmail=currentEmail}}}
|
||||
{{/d-modal-body}}
|
||||
|
||||
{{modal-footer-close closeModal=(action "closeModal")}}
|
||||
@ -1,20 +0,0 @@
|
||||
<form>
|
||||
{{#d-modal-body title="topic.auto_close_title" autoFocus="false"}}
|
||||
{{auto-close-form autoCloseTime=model.auto_close_time
|
||||
autoCloseValid=auto_close_valid
|
||||
autoCloseBasedOnLastPost=model.details.auto_close_based_on_last_post
|
||||
limited=model.details.auto_close_based_on_last_post }}
|
||||
{{#if willCloseImmediately}}
|
||||
<div class="warning">
|
||||
{{fa-icon "warning"}}
|
||||
{{willCloseI18n}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/d-modal-body}}
|
||||
<div class="modal-footer">
|
||||
{{d-button class="btn-primary" disabled=disable_submit label="topic.auto_close_save" action="saveAutoClose"}}
|
||||
<a {{action "closeModal"}}>{{i18n 'cancel'}}</a>
|
||||
{{conditional-loading-spinner size="small" condition=loading}}
|
||||
{{d-button class="pull-right" action="removeAutoClose" label="topic.auto_close_remove"}}
|
||||
</div>
|
||||
</form>
|
||||
@ -0,0 +1,103 @@
|
||||
<form>
|
||||
{{#d-modal-body title="topic.topic_status_update.title" autoFocus="false"}}
|
||||
<div class="radios">
|
||||
{{radio-button
|
||||
disabled=disableAutoClose
|
||||
name="auto-close"
|
||||
id="auto-close"
|
||||
value=closeStatusType
|
||||
selection=selection}}
|
||||
|
||||
<label class="radio" for="auto-close">
|
||||
{{fa-icon "clock-o"}} {{fa-icon "lock"}}
|
||||
|
||||
{{#if model.closed}}
|
||||
{{i18n 'topic.temp_open.title'}}
|
||||
{{else}}
|
||||
{{i18n 'topic.auto_close.title'}}
|
||||
{{/if}}
|
||||
</label>
|
||||
|
||||
{{radio-button
|
||||
disabled=disableAutoOpen
|
||||
name="auto-reopen"
|
||||
id="auto-reopen"
|
||||
value=openStatusType
|
||||
selection=selection}}
|
||||
|
||||
<label class="radio" for="auto-reopen">
|
||||
{{fa-icon "clock-o"}} {{fa-icon "unlock"}}
|
||||
|
||||
{{#if model.closed}}
|
||||
{{i18n 'topic.auto_reopen.title'}}
|
||||
{{else}}
|
||||
{{i18n 'topic.temp_close.title'}}
|
||||
{{/if}}
|
||||
</label>
|
||||
|
||||
{{radio-button
|
||||
disabled=disablePublishToCategory
|
||||
name="publish-to-category"
|
||||
id="publish-to-category"
|
||||
value=publishToCategoryStatusType
|
||||
selection=selection}}
|
||||
|
||||
<label class="radio" for="publish-to-category">
|
||||
{{fa-icon "clock-o"}} {{i18n 'topic.publish_to_category.title'}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{#if autoOpen}}
|
||||
{{auto-update-input
|
||||
inputLabelKey='topic.topic_status_update.time'
|
||||
input=updateTime
|
||||
inputValid=updateTimeValid
|
||||
hideBasedOnLastPost=true
|
||||
basedOnLastPost=false}}
|
||||
{{else if publishToCategory}}
|
||||
<div class="control-group">
|
||||
<label>{{i18n 'topic.topic_status_update.publish_to'}}</label>
|
||||
{{category-chooser valueAttribute="id" value=categoryId excludeCategoryId=excludeCategoryId}}
|
||||
</div>
|
||||
|
||||
{{auto-update-input
|
||||
inputLabelKey='topic.topic_status_update.time'
|
||||
input=updateTime
|
||||
inputValid=updateTimeValid
|
||||
hideBasedOnLastPost=true
|
||||
basedOnLastPost=false}}
|
||||
{{else if autoClose}}
|
||||
{{auto-update-input
|
||||
inputLabelKey='topic.topic_status_update.time'
|
||||
input=updateTime
|
||||
inputValid=updateTimeValid
|
||||
limited=topicStatusUpdate.based_on_last_post
|
||||
basedOnLastPost=topicStatusUpdate.based_on_last_post}}
|
||||
|
||||
{{#if willCloseImmediately}}
|
||||
<div class="warning">
|
||||
{{fa-icon "warning"}}
|
||||
{{willCloseI18n}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{d-button class="btn-primary"
|
||||
disabled=saveDisabled
|
||||
label="topic.topic_status_update.save"
|
||||
action="saveStatusUpdate"}}
|
||||
|
||||
<a {{action "closeModal"}}>{{i18n 'cancel'}}</a>
|
||||
{{conditional-loading-spinner size="small" condition=loading}}
|
||||
|
||||
{{#if topicStatusUpdate.execute_at}}
|
||||
{{d-button class="pull-right btn-danger"
|
||||
action="removeStatusUpdate"
|
||||
label='topic.topic_status_update.remove'}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</form>
|
||||
@ -1,12 +1,14 @@
|
||||
{{#d-modal-body}}
|
||||
{{#if emailSent}}
|
||||
{{{i18n 'login.sent_activation_email_again' currentEmail=currentEmail}}}
|
||||
{{else}}
|
||||
{{{i18n 'login.not_activated' sentTo=sentTo}}}
|
||||
<a href {{action "sendActivationEmail"}} class="resend-link">{{i18n 'login.resend_activation_email'}}</a>
|
||||
{{/if}}
|
||||
{{{i18n 'login.not_activated' sentTo=sentTo}}}
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class='btn btn-primary' {{action "closeModal"}}>{{i18n 'close'}}</button>
|
||||
{{d-button action="sendActivationEmail"
|
||||
label="login.resend_title"
|
||||
icon="envelope"
|
||||
class="btn-primary resend"}}
|
||||
{{d-button action="editActivationEmail"
|
||||
label="login.change_email"
|
||||
icon="pencil"
|
||||
class="edit-email"}}
|
||||
</div>
|
||||
|
||||
@ -263,12 +263,14 @@
|
||||
<div class="controls category-controls">
|
||||
<a href="{{unbound model.watchingTopicsPath}}">{{i18n 'user.watched_topics_link'}}</a>
|
||||
</div>
|
||||
<div class="instructions"></div>
|
||||
<div class="controls category-controls">
|
||||
<label><span class="icon fa fa-circle tracking"></span> {{i18n 'user.tracked_categories'}}</label>
|
||||
{{category-selector categories=model.trackedCategories blacklist=selectedCategories}}
|
||||
</div>
|
||||
<div class="instructions">{{i18n 'user.tracked_categories_instructions'}}</div>
|
||||
<div class="controls category-controls">
|
||||
<a href="{{unbound model.trackingTopicsPath}}">{{i18n 'user.tracked_topics_link'}}</a>
|
||||
</div>
|
||||
<div class="controls category-controls">
|
||||
<label><span class="icon fa fa-dot-circle-o watching-first-post"></span> {{i18n 'user.watched_first_post_categories'}}</label>
|
||||
{{category-selector categories=model.watchedFirstPostCategories}}
|
||||
|
||||
@ -81,7 +81,7 @@
|
||||
toggleClosed=(action "toggleClosed")
|
||||
toggleArchived=(action "toggleArchived")
|
||||
toggleVisibility=(action "toggleVisibility")
|
||||
showAutoClose=(action "topicRouteAction" "showAutoClose")
|
||||
showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate")
|
||||
showFeatureTopic=(action "topicRouteAction" "showFeatureTopic")
|
||||
showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp")
|
||||
convertToPublicTopic=(action "convertToPublicTopic")
|
||||
@ -107,7 +107,7 @@
|
||||
toggleClosed=(action "toggleClosed")
|
||||
toggleArchived=(action "toggleArchived")
|
||||
toggleVisibility=(action "toggleVisibility")
|
||||
showAutoClose=(action "topicRouteAction" "showAutoClose")
|
||||
showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate")
|
||||
showFeatureTopic=(action "topicRouteAction" "showFeatureTopic")
|
||||
showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp")
|
||||
convertToPublicTopic=(action "convertToPublicTopic")
|
||||
@ -174,7 +174,7 @@
|
||||
{{#conditional-loading-spinner condition=model.postStream.loadingFilter}}
|
||||
{{#if loadedAllPosts}}
|
||||
|
||||
{{topic-closing topic=model}}
|
||||
{{topic-status-info topic=model}}
|
||||
{{#if session.showSignupCta}}
|
||||
{{! replace "Log In to Reply" with the infobox }}
|
||||
{{signup-cta}}
|
||||
@ -188,7 +188,7 @@
|
||||
toggleClosed=(action "toggleClosed")
|
||||
toggleArchived=(action "toggleArchived")
|
||||
toggleVisibility=(action "toggleVisibility")
|
||||
showAutoClose=(action "topicRouteAction" "showAutoClose")
|
||||
showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate")
|
||||
showFeatureTopic=(action "topicRouteAction" "showFeatureTopic")
|
||||
showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp")
|
||||
convertToPublicTopic=(action "convertToPublicTopic")
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
{{d-button icon="plus" action="showInvite" label="user.invited.create" class="btn"}}
|
||||
{{#if canBulkInvite}}
|
||||
{{csv-uploader uploading=uploading}}
|
||||
<a href="https://meta.discourse.org/t/sending-bulk-user-invites/16468" target="_blank" style="color:black;">{{fa-icon "question-circle"}}</a>
|
||||
{{/if}}
|
||||
{{#if showReinviteAllButton}}
|
||||
{{#if reinvitedAll}}
|
||||
|
||||
@ -3,12 +3,13 @@ import { avatarFor } from 'discourse/widgets/post';
|
||||
import { iconNode } from 'discourse/helpers/fa-icon-node';
|
||||
import { h } from 'virtual-dom';
|
||||
import { dateNode } from 'discourse/helpers/node';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export function avatarAtts(user) {
|
||||
return { template: user.avatar_template,
|
||||
username: user.username,
|
||||
post_url: user.post_url,
|
||||
url: Discourse.getURL('/users/') + user.username_lower };
|
||||
url: userPath(user.username_lower) };
|
||||
}
|
||||
|
||||
createWidget('small-user-list', {
|
||||
|
||||
@ -2,6 +2,7 @@ import { createWidget, applyDecorators } from 'discourse/widgets/widget';
|
||||
import { h } from 'virtual-dom';
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
const flatten = array => [].concat.apply([], array);
|
||||
|
||||
@ -19,7 +20,7 @@ createWidget('priority-faq-link', {
|
||||
click(e) {
|
||||
e.preventDefault();
|
||||
if (this.siteSettings.faq_url === this.attrs.href) {
|
||||
ajax("/users/read-faq", { method: "POST" }).then(() => {
|
||||
ajax(userPath("read-faq"), { method: "POST" }).then(() => {
|
||||
this.currentUser.set('read_faq', true);
|
||||
DiscourseURL.routeToTag($(e.target).closest('a')[0]);
|
||||
});
|
||||
|
||||
@ -166,6 +166,11 @@ createWidget('header-buttons', {
|
||||
|
||||
const forceContextEnabled = ['category', 'user', 'private_messages'];
|
||||
|
||||
let additionalPanels = [];
|
||||
export function attachAdditionalPanel(name, toggle, transformAttrs) {
|
||||
additionalPanels.push({ name, toggle, transformAttrs });
|
||||
}
|
||||
|
||||
export default createWidget('header', {
|
||||
tagName: 'header.d-header.clearfix',
|
||||
buildKey: () => `header`,
|
||||
@ -214,6 +219,12 @@ export default createWidget('header', {
|
||||
panels.push(this.attach('user-menu'));
|
||||
}
|
||||
|
||||
additionalPanels.map((panel) => {
|
||||
if (this.state[panel.toggle]) {
|
||||
panels.push(this.attach(panel.name, panel.transformAttrs.call(this, attrs, state)));
|
||||
}
|
||||
});
|
||||
|
||||
const contents = [ this.attach('home-logo', { minimized: !!attrs.topic }),
|
||||
h('div.panel.clearfix', panels) ];
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import { h } from 'virtual-dom';
|
||||
import { emojiUnescape } from 'discourse/lib/text';
|
||||
import { postUrl, escapeExpression } from 'discourse/lib/utilities';
|
||||
import { setTransientHeader } from 'discourse/lib/ajax';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
const LIKED_TYPE = 5;
|
||||
const INVITED_TYPE = 8;
|
||||
@ -45,11 +46,11 @@ createWidget('notification-item', {
|
||||
}
|
||||
|
||||
if (attrs.notification_type === INVITED_TYPE) {
|
||||
return Discourse.getURL('/users/' + data.display_username);
|
||||
return userPath(data.display_username);
|
||||
}
|
||||
|
||||
if (data.group_id) {
|
||||
return Discourse.getURL('/users/' + data.username + '/messages/group/' + data.group_name);
|
||||
return userPath(data.username + '/messages/group/' + data.group_name);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -122,12 +122,13 @@ export default createWidget('topic-admin-menu', {
|
||||
action: 'toggleClosed',
|
||||
icon: 'lock',
|
||||
label: 'actions.close' });
|
||||
buttons.push({ className: 'topic-admin-autoclose',
|
||||
action: 'showAutoClose',
|
||||
icon: 'clock-o',
|
||||
label: 'actions.auto_close' });
|
||||
}
|
||||
|
||||
buttons.push({ className: 'topic-admin-status-update',
|
||||
action: 'showTopicStatusUpdate',
|
||||
icon: 'clock-o',
|
||||
label: 'actions.timed_update' });
|
||||
|
||||
const isPrivateMessage = topic.get('isPrivateMessage');
|
||||
|
||||
if (!isPrivateMessage && topic.get('visible')) {
|
||||
|
||||
@ -159,8 +159,7 @@ createWidget('topic-map-expanded', {
|
||||
if (l.title && l.title.length) {
|
||||
const domain = l.domain;
|
||||
if (domain && domain.length) {
|
||||
const s = domain.split('.');
|
||||
host = h('span.domain', s[s.length-2] + "." + s[s.length-1]);
|
||||
host = h('span.domain', domain);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -154,8 +154,7 @@ createWidget('timeline-scrollarea', {
|
||||
|
||||
if (this.state.position !== result.current) {
|
||||
this.state.position = result.current;
|
||||
const timeline = this._findAncestorWithProperty('updatePosition');
|
||||
timeline.updatePosition.call(timeline, result.current);
|
||||
this.sendWidgetAction('updatePosition', result.current);
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -287,8 +286,6 @@ export default createWidget('topic-timeline', {
|
||||
|
||||
this.state.position = pos;
|
||||
this.state.excerpt = "";
|
||||
this.scheduleRerender();
|
||||
|
||||
const stream = this.attrs.topic.get('postStream');
|
||||
|
||||
// a little debounce to avoid flashing
|
||||
@ -300,7 +297,6 @@ export default createWidget('topic-timeline', {
|
||||
// we have an off by one, stream is zero based,
|
||||
// pos is 1 based
|
||||
stream.excerpt(pos-1).then(info => {
|
||||
|
||||
if (info && this.state.position === pos) {
|
||||
let excerpt = "";
|
||||
|
||||
@ -308,9 +304,7 @@ export default createWidget('topic-timeline', {
|
||||
excerpt = "<span class='username'>" + info.username + ":</span> ";
|
||||
}
|
||||
|
||||
excerpt += info.excerpt;
|
||||
|
||||
this.state.excerpt = excerpt;
|
||||
this.state.excerpt = excerpt + info.excerpt;
|
||||
this.scheduleRerender();
|
||||
}
|
||||
});
|
||||
@ -332,22 +326,20 @@ export default createWidget('topic-timeline', {
|
||||
}
|
||||
|
||||
let elems = [h('h2', this.attach('link', {
|
||||
contents: ()=>titleHTML,
|
||||
className: 'fancy-title',
|
||||
action: 'jumpTop'}))];
|
||||
|
||||
contents: () => titleHTML,
|
||||
className: 'fancy-title',
|
||||
action: 'jumpTop'
|
||||
}))];
|
||||
|
||||
if (this.state.excerpt) {
|
||||
elems.push(
|
||||
new RawHtml({
|
||||
html: "<div class='post-excerpt'>" + this.state.excerpt + "</div>"
|
||||
}));
|
||||
elems.push(new RawHtml({
|
||||
html: "<div class='post-excerpt'>" + this.state.excerpt + "</div>"
|
||||
}));
|
||||
}
|
||||
|
||||
result.push(h('div.title', elems));
|
||||
}
|
||||
|
||||
|
||||
if (!attrs.fullScreen && currentUser && currentUser.get('canManageTopic')) {
|
||||
result.push(h('div.timeline-controls', this.attach('topic-admin-menu-button', { topic })));
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ export function register(helper, codeName, args, emitter) {
|
||||
start: new RegExp("\\[" + codeName + "(=[^\\[\\]]+)?\\]([\\s\\S]*)", "igm"),
|
||||
stop: new RegExp("\\[\\/" + codeName + "\\]", "igm"),
|
||||
emitter(blockContents, matches) {
|
||||
|
||||
|
||||
const options = helper.getOptions();
|
||||
while (blockContents.length && (typeof blockContents[0] === "string" || blockContents[0] instanceof String)) {
|
||||
blockContents[0] = String(blockContents[0]).replace(/^\s+/, '');
|
||||
@ -22,7 +24,11 @@ export function register(helper, codeName, args, emitter) {
|
||||
let contents = [];
|
||||
if (blockContents.length) {
|
||||
const nextContents = blockContents.slice(1);
|
||||
blockContents = this.processBlock(blockContents[0], nextContents).concat(nextContents);
|
||||
blockContents = this.processBlock(blockContents[0], nextContents);
|
||||
|
||||
nextContents.forEach(nc => {
|
||||
blockContents = blockContents.concat(this.processBlock(nc, []));
|
||||
});
|
||||
|
||||
blockContents.forEach(bc => {
|
||||
if (typeof bc === "string" || bc instanceof String) {
|
||||
|
||||
@ -38,7 +38,7 @@ export function setup(helper) {
|
||||
|
||||
const type = mentionLookup && mentionLookup(name);
|
||||
if (type === "user") {
|
||||
return ['a', {'class': 'mention', href: opts.getURL("/users/") + name.toLowerCase()}, mention];
|
||||
return ['a', {'class': 'mention', href: opts.getURL("/u/") + name.toLowerCase()}, mention];
|
||||
} else if (type === "group") {
|
||||
return ['a', {'class': 'mention-group', href: opts.getURL("/groups/") + name}, mention];
|
||||
} else {
|
||||
|
||||
@ -10,6 +10,7 @@ registerOption((siteSettings, opts) => {
|
||||
|
||||
export function setup(helper) {
|
||||
register(helper, 'quote', {noWrap: true, singlePara: true}, (contents, bbParams, options) => {
|
||||
|
||||
const params = {'class': 'quote'};
|
||||
let username = null;
|
||||
const opts = helper.getOptions();
|
||||
|
||||
@ -133,16 +133,7 @@ div.ac-wrap {
|
||||
}
|
||||
}
|
||||
|
||||
.auto-close-fields {
|
||||
div:not(:first-child) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
label {
|
||||
font-size: 1em;
|
||||
}
|
||||
input {
|
||||
width: 150px;
|
||||
}
|
||||
.auto-update-input {
|
||||
.examples {
|
||||
color: lighten($primary, 40%);
|
||||
}
|
||||
@ -187,5 +178,3 @@ div.ac-wrap {
|
||||
.cooked > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -295,34 +295,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.edit-auto-close-modal {
|
||||
.btn.pull-right {
|
||||
margin-right: 10px;
|
||||
}
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
.auto-close-fields {
|
||||
i.fa-clock-o {
|
||||
font-size: 1.143em;
|
||||
}
|
||||
input {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-category-modal {
|
||||
.auto-close-fields, .num-featured-topics-fields, .position-fields {
|
||||
.auto-update-input, .num-featured-topics-fields, .position-fields {
|
||||
input[type=text] {
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.auto-close-fields label {
|
||||
font-size: .929em;
|
||||
}
|
||||
|
||||
.subcategory-list-style-field {
|
||||
margin-left: 16px;
|
||||
}
|
||||
@ -398,3 +377,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.modal-button-bar {
|
||||
margin-top: 1em;
|
||||
|
||||
button {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
.fa.muted {
|
||||
.fa.muted, .fa.watching-first-post {
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 60%));
|
||||
}
|
||||
.fa.tracking, .fa.watching {
|
||||
|
||||
@ -93,6 +93,12 @@ $tag-color: scale-color($primary, $lightness: 40%);
|
||||
}
|
||||
}
|
||||
|
||||
.d-header .topic-header-extra {
|
||||
display: inline-block;
|
||||
.discourse-tags { display: inline-block; }
|
||||
.topic-featured-link { margin-left: 8px; }
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice.discourse-tag-select2 {
|
||||
padding-top: 5px;
|
||||
-webkit-box-shadow: none;
|
||||
|
||||
29
app/assets/stylesheets/common/base/topic-close-modal.scss
Normal file
29
app/assets/stylesheets/common/base/topic-close-modal.scss
Normal file
@ -0,0 +1,29 @@
|
||||
.topic-close-modal {
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.radios {
|
||||
padding-bottom: 20px;
|
||||
display: inline-block;
|
||||
|
||||
input[type='radio'] {
|
||||
vertical-align: middle;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
label {
|
||||
padding: 0 10px 0px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn.pull-right {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.auto-update-input {
|
||||
input {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -297,7 +297,3 @@ and (max-width : 600px) {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.user-preferences .watching-first-post.fa-dot-circle-o {
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
.show-topic-admin,
|
||||
#topic-progress,
|
||||
.quote-controls,
|
||||
#topic-closing-info,
|
||||
#topic-status-info,
|
||||
div.lazyYT,
|
||||
.post-info.edits,
|
||||
.post-action,
|
||||
|
||||
@ -342,7 +342,7 @@
|
||||
display: block;
|
||||
bottom: 8px;
|
||||
}
|
||||
.auto-close-fields .examples {
|
||||
.auto-update-input .examples {
|
||||
margin-top: 0;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
@ -46,15 +46,20 @@
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
margin: 7px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
h3 {
|
||||
display: inline-block;;
|
||||
font-size: 1.429em;
|
||||
padding: 10px 15px 7px;
|
||||
}
|
||||
.close {margin: 10px;}
|
||||
}
|
||||
.close {
|
||||
float: right;
|
||||
font-size: 1.429em;
|
||||
text-decoration: none;
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 35%), scale-color($secondary, $lightness: 65%));
|
||||
|
||||
@ -79,7 +79,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
#topic-closing-info {
|
||||
#topic-status-info {
|
||||
border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -75%);
|
||||
padding-top: 10px;
|
||||
height: 20px;
|
||||
@ -242,4 +242,3 @@ and (max-width : 485px) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -49,23 +49,24 @@
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
// we need tighter spacing on mobile for header
|
||||
// this clearfix under the modal title h3 pushes it way down
|
||||
.clearfix {
|
||||
display:none;
|
||||
}
|
||||
padding: 10px 0px;
|
||||
|
||||
h3 {
|
||||
display: inline-block;
|
||||
font-size: 1.286em;
|
||||
padding-left: 15px;
|
||||
margin-bottom: 5px;
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
.close {
|
||||
float: right;
|
||||
font-size: 1.714em;
|
||||
padding: 10px 15px 5px 5px;
|
||||
margin: -15px 0 0 0;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
|
||||
@ -475,7 +475,9 @@ button.select-post {
|
||||
|
||||
|
||||
.deleted {
|
||||
background-color: dark-light-diff(rgba($danger,.7), $secondary, 50%, -60%);
|
||||
.topic-body {
|
||||
background-color: dark-light-diff(rgba($danger,.7), $secondary, 50%, -60%);
|
||||
}
|
||||
}
|
||||
|
||||
.deleted-user-avatar {
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#topic-closing-info {
|
||||
#topic-status-info {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@ -190,7 +190,7 @@ sup sup, sub sup, sup sub, sub sub { top: 0; }
|
||||
}
|
||||
|
||||
// make mobile timeline top and bottom dates easier to select
|
||||
.topic-timeline {
|
||||
.topic-timeline {
|
||||
.start-date { font-size: 110%; padding: 5px; }
|
||||
.now-date { font-size: 110%; padding: 5px; }
|
||||
}
|
||||
|
||||
@ -69,6 +69,19 @@ class Admin::EmailController < Admin::AdminController
|
||||
end
|
||||
end
|
||||
|
||||
def smtp_should_reject
|
||||
params.require(:from)
|
||||
params.require(:to)
|
||||
# These strings aren't localized; they are sent to an anonymous SMTP user.
|
||||
if !User.exists?(email: Email.downcase(params[:from])) && !SiteSetting.enable_staged_users
|
||||
render json: { reject: true, reason: "Mail from your address is not accepted. Do you have an account here?" }
|
||||
elsif Email::Receiver.check_address(Email.downcase(params[:to])).nil?
|
||||
render json: { reject: true, reason: "Mail to this address is not accepted. Check the address and try to send again?" }
|
||||
else
|
||||
render json: { reject: false }
|
||||
end
|
||||
end
|
||||
|
||||
def handle_mail
|
||||
params.require(:email)
|
||||
Email::Processor.process!(params[:email])
|
||||
|
||||
@ -7,12 +7,9 @@ class Admin::ReportsController < Admin::AdminController
|
||||
|
||||
raise Discourse::NotFound unless report_type =~ /^[a-z0-9\_]+$/
|
||||
|
||||
start_date = 1.month.ago
|
||||
start_date = Time.parse(params[:start_date]) if params[:start_date].present?
|
||||
|
||||
end_date = start_date + 1.month
|
||||
end_date = Time.parse(params[:end_date]) if params[:end_date].present?
|
||||
|
||||
start_date = params[:start_date].present? ? Time.parse(params[:start_date]) : 30.days.ago
|
||||
end_date = params[:end_date].present? ? Time.parse(params[:end_date]) : start_date + 30.days
|
||||
|
||||
if params.has_key?(:category_id) && params[:category_id].to_i > 0
|
||||
category_id = params[:category_id].to_i
|
||||
else
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user