diff --git a/Gemfile.lock b/Gemfile.lock
index 32ab0c59b2..c9b6f3ddc1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -150,7 +150,7 @@ GEM
mail (2.6.6)
mime-types (>= 1.16, < 4)
memory_profiler (0.9.8)
- message_bus (2.0.3)
+ message_bus (2.0.5)
rack (>= 1.1.3)
metaclass (0.0.4)
method_source (0.8.2)
@@ -214,7 +214,7 @@ GEM
omniauth-twitter (1.3.0)
omniauth-oauth (~> 1.1)
rack
- onebox (1.8.17)
+ onebox (1.8.18)
fast_blank (>= 1.0.0)
htmlentities (~> 4.3)
moneta (~> 1.0)
diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6
index f63b80994f..ac3c82689f 100644
--- a/app/assets/javascripts/discourse/components/d-editor.js.es6
+++ b/app/assets/javascripts/discourse/components/d-editor.js.es6
@@ -236,7 +236,7 @@ export default Ember.Component.extend({
const shortcuts = this.get('toolbar.shortcuts');
// for some reason I am having trouble bubbling this so hack it in
- mouseTrap.bind(['ctrl+/','command+/'], (event) =>{
+ mouseTrap.bind(['ctrl+shift+s','command+shift+s'], (event) =>{
this.appEvents.trigger('header:keyboard-trigger', {type: 'search', event});
return true;
});
@@ -259,7 +259,7 @@ export default Ember.Component.extend({
if (this.get('composerEvents')) {
this.appEvents.on('composer:insert-block', text => this._addBlock(this._getSelected(), text));
- this.appEvents.on('composer:insert-text', text => this._addText(this._getSelected(), text));
+ this.appEvents.on('composer:insert-text', (text, options) => this._addText(this._getSelected(), text, options));
this.appEvents.on('composer:replace-text', (oldVal, newVal) => this._replaceText(oldVal, newVal));
}
this._mouseTrap = mouseTrap;
@@ -613,8 +613,22 @@ export default Ember.Component.extend({
Ember.run.scheduleOnce("afterRender", () => $textarea.focus());
},
- _addText(sel, text) {
+ _addText(sel, text, options) {
const $textarea = this.$('textarea.d-editor-input');
+
+ if (options && options.ensureSpace) {
+ if ((sel.pre + '').length > 0) {
+ if (!sel.pre.match(/\s$/)) {
+ text = ' ' + text;
+ }
+ }
+ if ((sel.post + '').length > 0) {
+ if (!sel.post.match(/^\s/)) {
+ text = text + ' ';
+ }
+ }
+ }
+
const insert = `${sel.pre}${text}`;
const value = `${insert}${sel.post}`;
this.set('value', value);
diff --git a/app/assets/javascripts/discourse/components/footer-message.js.es6 b/app/assets/javascripts/discourse/components/footer-message.js.es6
new file mode 100644
index 0000000000..1d6d852838
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/footer-message.js.es6
@@ -0,0 +1,3 @@
+export default Ember.Component.extend({
+ classNames: ['footer-message']
+});
diff --git a/app/assets/javascripts/discourse/components/queued-post.js.es6 b/app/assets/javascripts/discourse/components/queued-post.js.es6
index bd1e3292d1..b0b40c996b 100644
--- a/app/assets/javascripts/discourse/components/queued-post.js.es6
+++ b/app/assets/javascripts/discourse/components/queued-post.js.es6
@@ -19,13 +19,14 @@ function updateState(state, opts) {
export default Ember.Component.extend(bufferedProperty('editables'), {
editing: propertyEqual('post', 'currentlyEditing'),
- editables: {},
+ editables: null,
_confirmDelete: updateState('rejected', {deleteUser: true}),
_initEditables: function() {
const post = this.get('post');
const postOptions = post.get('post_options');
+ this.set('editables', {});
this.set('editables.raw', post.get('raw'));
this.set('editables.category', post.get('category'));
this.set('editables.category_id', post.get('category.id'));
diff --git a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6
index 1979fa984f..6ae50d0475 100644
--- a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6
+++ b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6
@@ -6,8 +6,8 @@ const bindings = {
'!': {postAction: 'showFlags'},
'#': {handler: 'goToPost', anonymous: true},
'/': {handler: 'toggleSearch', anonymous: true},
- 'ctrl+/': {handler: 'toggleSearch', anonymous: true},
- 'command+/': {handler: 'toggleSearch', anonymous: true},
+ 'ctrl+shift+s': {handler: 'toggleSearch', anonymous: true},
+ 'command+shift+s': {handler: 'toggleSearch', anonymous: true},
'=': {handler: 'toggleHamburgerMenu', anonymous: true},
'?': {handler: 'showHelpModal', anonymous: true},
'.': {click: '.alert.alert-info.clickable', anonymous: true}, // show incoming/updated topics
diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs
index 9d65972904..19cdd9116d 100644
--- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs
+++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs
@@ -64,7 +64,7 @@
{{d-icon "check"}} {{i18n 'topics.bulk.dismiss_new'}}
{{/if}}
- {{#footer-message education=footerEducation message=footerMessage tagName=""}}
+ {{#footer-message education=footerEducation message=footerMessage}}
{{#if latest}}
{{#if canCreateTopicOnCategory}}{{i18n 'topic.suggest_create_topic'}}{{/if}}
{{else if top}}
diff --git a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs
index 4eb7503e29..5c07afdc59 100644
--- a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs
+++ b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs
@@ -40,29 +40,15 @@
{{i18n 'topics.bulk.dismiss_new'}}
{{/if}}
- {{#if latest}}
-
- {{{footerEducation}}}
-
-
- {{else}}
- {{#if top}}
-
- {{#link-to "discovery.categories"}}{{i18n 'topic.browse_all_categories'}}{{/link-to}}, {{#link-to 'discovery.latest'}}{{i18n 'topic.view_latest_topics'}}{{/link-to}} {{i18n 'or'}} {{i18n 'filters.top.other_periods'}}
-
- {{top-period-buttons period=period action="changePeriod"}}
-
+ {{#footer-message education=footerEducation message=footerMessage}}
+ {{#if latest}}
+ {{#if canCreateTopicOnCategory}}{{i18n 'topic.suggest_create_topic'}}{{/if}}
+ {{else if top}}
+ {{#link-to "discovery.categories"}}{{i18n 'topic.browse_all_categories'}}{{/link-to}}, {{#link-to 'discovery.latest'}}{{i18n 'topic.view_latest_topics'}}{{/link-to}} {{i18n 'or'}} {{i18n 'filters.top.other_periods'}}
+ {{top-period-buttons period=period action="changePeriod"}}
{{else}}
-
- {{{footerEducation}}}
-
-
- {{footerMessage}}{{#link-to "discovery.categories"}} {{i18n 'topic.browse_all_categories'}}{{/link-to}} {{i18n 'or'}} {{#link-to 'discovery.latest'}}{{i18n 'topic.view_latest_topics'}}{{/link-to}}
-
+ {{#link-to "discovery.categories"}} {{i18n 'topic.browse_all_categories'}}{{/link-to}} {{i18n 'or'}} {{#link-to 'discovery.latest'}}{{i18n 'topic.view_latest_topics'}}{{/link-to}}
{{/if}}
- {{/if}}
+ {{/footer-message}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/widgets/search-menu-results.js.es6 b/app/assets/javascripts/discourse/widgets/search-menu-results.js.es6
index c195c962f6..8b10b12e3d 100644
--- a/app/assets/javascripts/discourse/widgets/search-menu-results.js.es6
+++ b/app/assets/javascripts/discourse/widgets/search-menu-results.js.es6
@@ -20,8 +20,8 @@ class Highlighted extends RawHtml {
function createSearchResult({ type, linkField, builder }) {
return createWidget(`search-result-${type}`, {
html(attrs) {
- return attrs.results.map(r => {
+ return attrs.results.map(r => {
let searchResultId;
if (type === "topic") {
searchResultId = r.get('topic_id');
diff --git a/app/assets/javascripts/discourse/widgets/search-menu.js.es6 b/app/assets/javascripts/discourse/widgets/search-menu.js.es6
index d5e9b12e9a..b5c0f840f3 100644
--- a/app/assets/javascripts/discourse/widgets/search-menu.js.es6
+++ b/app/assets/javascripts/discourse/widgets/search-menu.js.es6
@@ -132,7 +132,7 @@ export default createWidget('search-menu', {
noResults: searchData.noResults,
results: searchData.results,
invalidTerm: searchData.invalidTerm,
- searchContextEnabled: searchData.contextEnabled
+ searchContextEnabled: searchData.contextEnabled,
}));
}
}
@@ -169,6 +169,75 @@ export default createWidget('search-menu', {
this.sendWidgetAction('toggleSearchMenu');
},
+ keyDown(e) {
+ if (searchData.loading || searchData.noResults) {
+ return;
+ }
+
+ if (e.which === 65 /* a */) {
+ let focused = $('header .results .search-link:focus');
+ if (focused.length === 1) {
+ if ($('#reply-control.open').length === 1) {
+ // add a link and focus composer
+
+ this.appEvents.trigger('composer:insert-text', focused[0].href, {ensureSpace: true});
+ this.appEvents.trigger('header:keyboard-trigger', {type: 'search'});
+
+ e.preventDefault();
+ $('#reply-control.open textarea').focus();
+ return false;
+ }
+ }
+ }
+
+ const up = e.which === 38;
+ const down = e.which === 40;
+ if (up || down) {
+
+ let focused = $('header .panel-body *:focus')[0];
+
+ if (!focused) {
+ return;
+ }
+
+ let links = $('header .panel-body .results a');
+ let results = $('header .panel-body .results .search-link');
+
+ let prevResult;
+ let result;
+
+ links.each((idx,item) => {
+ if ($(item).hasClass('search-link')) {
+ prevResult = item;
+ }
+
+ if (item === focused) {
+ result = prevResult;
+ }
+ });
+
+ let index = -1;
+
+ if (result) {
+ index = results.index(result);
+ }
+
+ if (index === -1 && down) {
+ $('header .panel-body .search-link:first').focus();
+ } else if (index === 0 && up) {
+ $('header .panel-body input:first').focus();
+ } else if (index > -1) {
+ index += (down ? 1 : -1);
+ if (index >= 0 && index < results.length) {
+ $(results[index]).focus();
+ }
+ }
+
+ e.preventDefault();
+ return false;
+ }
+ },
+
triggerSearch() {
searchData.noResults = false;
this.searchService().set('highlightTerm', searchData.term);
diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss
index 21e80fb44e..10d2de5b67 100644
--- a/app/assets/stylesheets/common/base/menu-panel.scss
+++ b/app/assets/stylesheets/common/base/menu-panel.scss
@@ -18,7 +18,7 @@
}
.menu-panel {
- border: 1px solid $primary-low;
+ border: 1px solid $primary-low;
box-shadow: 0 2px 2px rgba(0,0,0, .25);
background-color: $secondary;
z-index: 1100;
@@ -148,6 +148,7 @@
padding: 5px;
text-align: center;
}
+
.filter {
padding: 0;
&:hover {background: transparent;}
diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss
index 07d645809f..b5113638e2 100644
--- a/app/assets/stylesheets/desktop/user.scss
+++ b/app/assets/stylesheets/desktop/user.scss
@@ -92,6 +92,11 @@
.user-table {
margin-top: 30px;
width: 100%;
+ display: table;
+ table-layout: fixed;
+ .wrapper {
+ display: table-row;
+ }
}
.user-navigation .nav-stacked .glyph {
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index ca69ac2f14..fa99d1f2d9 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -574,6 +574,9 @@ class PostsController < ApplicationController
params[:skip_validations] = params[:skip_validations].to_s == "true"
permitted << :skip_validations
+ params[:import_mode] = params[:import_mode].to_s == "true"
+ permitted << :import_mode
+
# We allow `embed_url` via the API
permitted << :embed_url
diff --git a/app/models/backup.rb b/app/models/backup.rb
index 72a619a06c..38dd0b2a83 100644
--- a/app/models/backup.rb
+++ b/app/models/backup.rb
@@ -31,6 +31,7 @@ class Backup
def after_create_hook
upload_to_s3 if SiteSetting.enable_s3_backups?
+ DiscourseEvent.trigger(:backup_created)
end
def after_remove_hook
diff --git a/app/models/category_featured_topic.rb b/app/models/category_featured_topic.rb
index 22d948dd59..a517064374 100644
--- a/app/models/category_featured_topic.rb
+++ b/app/models/category_featured_topic.rb
@@ -23,7 +23,13 @@ class CategoryFeaturedTopic < ActiveRecord::Base
no_definitions: true
}
- # Add topics, even if they're in secured categories:
+ # It may seem a bit odd that we are running 2 queries here, when admin
+ # can clearly pull out all the topics needed.
+ # We do so, so anonymous will ALWAYS get some topics
+ # If we only fetched as admin we may have a situation where anon can see
+ # no featured topics (all the previous 2x topics are only visible to admins)
+
+ # Add topics, even if they're in secured categories or invisible
query = TopicQuery.new(CategoryFeaturedTopic.fake_admin, query_opts)
results = query.list_category_topic_ids(c).uniq
diff --git a/app/models/concerns/has_custom_fields.rb b/app/models/concerns/has_custom_fields.rb
index 139b598e28..25a6422b0e 100644
--- a/app/models/concerns/has_custom_fields.rb
+++ b/app/models/concerns/has_custom_fields.rb
@@ -139,7 +139,6 @@ module HasCustomFields
end
def custom_fields
-
if @preloaded_custom_fields
return @preloaded_proxy ||= PreloadedProxy.new(@preloaded_custom_fields)
end
@@ -177,7 +176,10 @@ module HasCustomFields
dup.delete(f.name)
end
else
- if dup[f.name] != f.value
+ t = {}
+ self.class.append_custom_field(t, f.name, f.value)
+
+ if dup[f.name] != t[f.name]
f.destroy
else
dup.delete(f.name)
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index bae43779c4..1ce75d44ed 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -2376,7 +2376,7 @@ en:
hamburger_menu: '= Open hamburger menu'
user_profile_menu: 'p Open user menu'
show_incoming_updated_topics: '. Show updated topics'
- search: '/ or ctrl+/ Search'
+ search: '/ or ctrl+shift+s Search'
help: '? Open keyboard help'
dismiss_new_posts: 'x, r Dismiss New/Posts'
dismiss_topics: 'x, t Dismiss Topics'
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index b02a01e761..334e87e7b9 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -2424,7 +2424,7 @@ en:
invited_group_to_private_message_body: |
%{username} invited @%{group_name} to a message
- > #### %{topic_title}
+ > **%{topic_title}**
>
> %{topic_excerpt}
diff --git a/lib/discourse.rb b/lib/discourse.rb
index f267534b2b..c2aa10a0ea 100644
--- a/lib/discourse.rb
+++ b/lib/discourse.rb
@@ -447,7 +447,7 @@ module Discourse
def self.reset_active_record_cache
ActiveRecord::Base.connection.query_cache.clear
- (ActiveRecord::Base.connection.tables - %w[schema_migrations]).each do |table|
+ (ActiveRecord::Base.connection.tables - %w[schema_migrations versions]).each do |table|
table.classify.constantize.reset_column_information rescue nil
end
nil
diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb
index 8207feae75..15db587294 100644
--- a/lib/plugin/instance.rb
+++ b/lib/plugin/instance.rb
@@ -92,7 +92,7 @@ class Plugin::Instance
def whitelist_staff_user_custom_field(field)
reloadable_patch do |plugin|
- User.register_plugin_staff_custom_field(field, plugin) if plugin.enabled?
+ ::User.register_plugin_staff_custom_field(field, plugin) if plugin.enabled?
end
end
@@ -248,21 +248,27 @@ class Plugin::Instance
end
end
+ def register_category_custom_field_type(name, type)
+ reloadable_patch do |plugin|
+ Category.register_custom_field_type(name, type) if plugin.enabled?
+ end
+ end
+
def register_topic_custom_field_type(name, type)
reloadable_patch do |plugin|
- Topic.register_custom_field_type(name, type) if plugin.enabled?
+ ::Topic.register_custom_field_type(name, type) if plugin.enabled?
end
end
def register_post_custom_field_type(name, type)
reloadable_patch do |plugin|
- Post.register_custom_field_type(name, type) if plugin.enabled?
+ ::Post.register_custom_field_type(name, type) if plugin.enabled?
end
end
def register_group_custom_field_type(name, type)
reloadable_patch do |plugin|
- Group.register_custom_field_type(name, type) if plugin.enabled?
+ ::Group.register_custom_field_type(name, type) if plugin.enabled?
end
end
diff --git a/lib/stylesheet/watcher.rb b/lib/stylesheet/watcher.rb
index 27dbe8f692..f0778b38dc 100644
--- a/lib/stylesheet/watcher.rb
+++ b/lib/stylesheet/watcher.rb
@@ -37,10 +37,14 @@ module Stylesheet
end
root = Rails.root.to_s
+
+ listener_opts = { ignore: /xxxx/ }
+ listener_opts[:force_polling] = true if ENV['FORCE_POLLING']
+
@paths.each do |watch|
Thread.new do
begin
- listener = Listen.to("#{root}/#{watch}", ignore: /xxxx/) do |modified, added, _|
+ listener = Listen.to("#{root}/#{watch}", listener_opts) do |modified, added, _|
paths = [modified, added].flatten
paths.compact!
paths.map! { |long| long[(root.length + 1)..-1] }
diff --git a/lib/version.rb b/lib/version.rb
index 8e27a89f97..bc025b9cb8 100644
--- a/lib/version.rb
+++ b/lib/version.rb
@@ -5,7 +5,7 @@ module Discourse
MAJOR = 1
MINOR = 9
TINY = 0
- PRE = 'beta6'
+ PRE = 'beta7'
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/spec/components/concern/has_custom_fields_spec.rb b/spec/components/concern/has_custom_fields_spec.rb
index 75ec51dd05..fc1f1a43e8 100644
--- a/spec/components/concern/has_custom_fields_spec.rb
+++ b/spec/components/concern/has_custom_fields_spec.rb
@@ -142,6 +142,16 @@ describe HasCustomFields do
test_item.reload
expect(test_item.custom_fields).to eq("bool" => true, "int" => 1, "json" => { "foo" => "bar" })
+
+ before_ids = CustomFieldsTestItemCustomField.where(custom_fields_test_item_id: test_item.id).pluck(:id)
+
+ test_item.custom_fields["bool"] = false
+ test_item.save
+
+ after_ids = CustomFieldsTestItemCustomField.where(custom_fields_test_item_id: test_item.id).pluck(:id)
+
+ # we updated only 1 custom field, so there should be only 1 different id
+ expect((before_ids - after_ids).size).to eq(1)
end
it "simple modifications don't interfere" do
diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb
index c5ffb51252..c27a7c9e0e 100644
--- a/spec/controllers/posts_controller_spec.rb
+++ b/spec/controllers/posts_controller_spec.rb
@@ -510,7 +510,7 @@ describe PostsController do
end
it "toggle wiki status should create a new version" do
- admin = log_in(:admin)
+ _admin = log_in(:admin)
another_user = Fabricate(:user)
another_post = Fabricate(:post, user: another_user)
@@ -520,7 +520,7 @@ describe PostsController do
expect { xhr :put, :wiki, post_id: another_post.id, wiki: 'false' }
.to change { another_post.reload.version }.by(-1)
- another_admin = log_in(:admin)
+ _another_admin = log_in(:admin)
expect { xhr :put, :wiki, post_id: another_post.id, wiki: 'true' }
.to change { another_post.reload.version }.by(1)
@@ -631,6 +631,46 @@ describe PostsController do
expect(response.body).to eq(original)
end
+
+ it 'allows to create posts in import_mode' do
+ NotificationEmailer.enable
+ post = Fabricate(:post)
+ user = Fabricate(:user)
+ master_key = ApiKey.create_master_key.key
+
+ xhr :post, :create,
+ api_username: user.username,
+ api_key: master_key,
+ raw: 'this is test reply 1',
+ topic_id: post.topic.id,
+ reply_to_post_number: 1
+
+ expect(response).to be_success
+ expect(post.topic.user.notifications.count).to eq(1)
+ post.topic.user.notifications.destroy_all
+
+ xhr :post, :create,
+ api_username: user.username,
+ api_key: master_key,
+ raw: 'this is test reply 2',
+ topic_id: post.topic.id,
+ reply_to_post_number: 1,
+ import_mode: true
+
+ expect(response).to be_success
+ expect(post.topic.user.notifications.count).to eq(0)
+
+ xhr :post, :create,
+ api_username: user.username,
+ api_key: master_key,
+ raw: 'this is test reply 3',
+ topic_id: post.topic.id,
+ reply_to_post_number: 1,
+ import_mode: false
+
+ expect(response).to be_success
+ expect(post.topic.user.notifications.count).to eq(1)
+ end
end
describe 'when logged in' do
diff --git a/spec/models/category_featured_topic_spec.rb b/spec/models/category_featured_topic_spec.rb
index ee102375d4..7c9a611e0f 100644
--- a/spec/models/category_featured_topic_spec.rb
+++ b/spec/models/category_featured_topic_spec.rb
@@ -34,7 +34,7 @@ describe CategoryFeaturedTopic do
it 'should feature stuff in the correct order' do
category = Fabricate(:category, num_featured_topics: 2)
- t5 = Fabricate(:topic, category_id: category.id, bumped_at: 12.minutes.ago)
+ _t5 = Fabricate(:topic, category_id: category.id, bumped_at: 12.minutes.ago)
t4 = Fabricate(:topic, category_id: category.id, bumped_at: 10.minutes.ago)
t3 = Fabricate(:topic, category_id: category.id, bumped_at: 7.minutes.ago)
t2 = Fabricate(:topic, category_id: category.id, bumped_at: 4.minutes.ago)
@@ -45,7 +45,7 @@ describe CategoryFeaturedTopic do
# Should find more than we need: pinned topics first, then num_featured_topics * 2
expect(
- CategoryFeaturedTopic.where(category_id: category.id).pluck(:topic_id)
+ CategoryFeaturedTopic.where(category_id: category.id).order('rank asc').pluck(:topic_id)
).to eq([pinned.id, t2.id, t1.id, t3.id, t4.id])
end