From 52763f5115a7fa7f8c592d373800b2d34ea20125 Mon Sep 17 00:00:00 2001 From: Erick Guan Date: Mon, 5 Dec 2016 13:31:43 +0100 Subject: [PATCH] FEATURE: Allow posting a link with topics --- .../discourse/components/composer-body.js.es6 | 5 +- .../discourse/controllers/history.js.es6 | 3 ++ .../discourse/controllers/topic.js.es6 | 8 ++++ .../helpers/topic-featured-link.js.es6 | 6 +++ .../lib/render-topic-featured-link.js.es6 | 46 +++++++++++++++++++ .../discourse/models/category.js.es6 | 12 +++++ .../discourse/models/composer.js.es6 | 40 ++++++++++++---- .../components/edit-category-settings.hbs | 11 +++++ .../templates/components/topic-category.hbs | 20 ++++---- .../discourse/templates/composer.hbs | 6 ++- .../templates/list/topic-list-item.raw.hbs | 3 ++ .../discourse/templates/modal/history.hbs | 7 +++ .../javascripts/discourse/templates/topic.hbs | 3 ++ .../widgets/header-topic-info.js.es6 | 10 +++- .../stylesheets/common/base/compose.scss | 4 ++ .../stylesheets/common/base/tagging.scss | 33 ++----------- app/assets/stylesheets/common/base/topic.scss | 21 ++++++++- .../common/components/badges.css.scss | 4 ++ app/assets/stylesheets/desktop/compose.scss | 9 ++++ .../stylesheets/desktop/topic-post.scss | 24 +++++++++- app/controllers/posts_controller.rb | 1 - app/helpers/application_helper.rb | 11 +++++ app/models/topic.rb | 13 ++++++ app/models/topic_list.rb | 2 + app/serializers/post_revision_serializer.rb | 4 ++ app/serializers/site_serializer.rb | 11 ++++- app/serializers/suggested_topic_serializer.rb | 10 +++- app/serializers/topic_list_item_serializer.rb | 11 ++++- app/serializers/topic_view_serializer.rb | 12 ++++- app/views/user_notifications/digest.html.erb | 6 +++ config/locales/client.en.yml | 2 + config/locales/server.en.yml | 8 ++++ config/site_settings.yml | 10 ++++ lib/discourse_featured_link.rb | 27 +++++++++++ lib/email/styles.rb | 5 ++ lib/guardian/category_guardian.rb | 5 ++ lib/guardian/topic_guardian.rb | 5 ++ lib/post_creator.rb | 20 +++++++- lib/post_revisor.rb | 17 +++++++ lib/topic_creator.rb | 4 ++ lib/validators/post_validator.rb | 11 +++-- spec/components/guardian_spec.rb | 23 ++++++++++ spec/components/post_creator_spec.rb | 9 ++++ .../validators/post_validator_spec.rb | 10 ++++ spec/controllers/posts_controller_spec.rb | 8 +--- .../fabricators/embeddable_host_fabricator.rb | 4 ++ spec/models/category_spec.rb | 10 ++-- spec/models/topic_spec.rb | 36 ++++++++++++++- spec/support/helpers.rb | 4 +- test/javascripts/models/composer-test.js.es6 | 6 ++- 50 files changed, 503 insertions(+), 77 deletions(-) create mode 100644 app/assets/javascripts/discourse/helpers/topic-featured-link.js.es6 create mode 100644 app/assets/javascripts/discourse/lib/render-topic-featured-link.js.es6 create mode 100644 lib/discourse_featured_link.rb diff --git a/app/assets/javascripts/discourse/components/composer-body.js.es6 b/app/assets/javascripts/discourse/components/composer-body.js.es6 index d3c1e96d2eb..aba22cf7284 100644 --- a/app/assets/javascripts/discourse/components/composer-body.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-body.js.es6 @@ -13,7 +13,8 @@ export default Ember.Component.extend({ 'composer.canEditTitle:edit-title', 'composer.createdPost:created-post', 'composer.creatingTopic:topic', - 'composer.whisper:composing-whisper'], + 'composer.whisper:composing-whisper', + 'composer.showComposerEditor::topic-featured-link-only'], @computed('composer.composeState') composeState(composeState) { @@ -27,7 +28,7 @@ export default Ember.Component.extend({ this.appEvents.trigger("composer:resized"); }, - @observes('composeState', 'composer.action') + @observes('composeState', 'composer.action', 'composer.canEditTopicFeaturedLink') resize() { Ember.run.scheduleOnce('afterRender', () => { if (!this.element || this.isDestroying || this.isDestroyed) { return; } diff --git a/app/assets/javascripts/discourse/controllers/history.js.es6 b/app/assets/javascripts/discourse/controllers/history.js.es6 index 42550be8803..e94852cf3ec 100644 --- a/app/assets/javascripts/discourse/controllers/history.js.es6 +++ b/app/assets/javascripts/discourse/controllers/history.js.es6 @@ -21,6 +21,9 @@ export default Ember.Controller.extend(ModalFunctionality, { if (this.site.mobileView) { this.set("viewMode", "inline"); } }.on("init"), + previousFeaturedLink: Em.computed.alias('model.featured_link_changes.previous'), + currentFeaturedLink: Em.computed.alias('model.featured_link_changes.current'), + previousTagChanges: customTagArray('model.tags_changes.previous'), currentTagChanges: customTagArray('model.tags_changes.current'), diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 377aebd76d5..4b9650fd98c 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -160,6 +160,14 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { return post => this.postSelected(post); }.property(), + @computed('model.isPrivateMessage', 'model.category.id') + canEditTopicFeaturedLink(isPrivateMessage, categoryId) { + if (!this.siteSettings.topic_featured_link_enabled || isPrivateMessage) { return false; } + + const categoryIds = this.site.get('topic_featured_link_allowed_category_ids'); + return categoryIds === undefined || !categoryIds.length || categoryIds.indexOf(categoryId) !== -1; + }, + @computed('model.isPrivateMessage') canEditTags(isPrivateMessage) { return !isPrivateMessage && this.site.get('can_tag_topics'); diff --git a/app/assets/javascripts/discourse/helpers/topic-featured-link.js.es6 b/app/assets/javascripts/discourse/helpers/topic-featured-link.js.es6 new file mode 100644 index 00000000000..686599e2b1f --- /dev/null +++ b/app/assets/javascripts/discourse/helpers/topic-featured-link.js.es6 @@ -0,0 +1,6 @@ +import { registerUnbound } from 'discourse-common/lib/helpers'; +import renderTopicFeaturedLink from 'discourse/lib/render-topic-featured-link'; + +export default registerUnbound('topic-featured-link', function(topic, params) { + return new Handlebars.SafeString(renderTopicFeaturedLink(topic, params)); +}); diff --git a/app/assets/javascripts/discourse/lib/render-topic-featured-link.js.es6 b/app/assets/javascripts/discourse/lib/render-topic-featured-link.js.es6 new file mode 100644 index 00000000000..c8c3d640f80 --- /dev/null +++ b/app/assets/javascripts/discourse/lib/render-topic-featured-link.js.es6 @@ -0,0 +1,46 @@ +import { extractDomainFromUrl } from 'discourse/lib/utilities'; +import { h } from 'virtual-dom'; + +const _decorators = []; + +export function addFeaturedLinkMetaDecorator(decorator) { + _decorators.push(decorator); +} + +function extractLinkMeta(topic) { + const href = topic.featured_link, target = Discourse.SiteSettings.open_topic_featured_link_in_external_window ? '_blank' : ''; + if (!href) { return; } + + let domain = extractDomainFromUrl(href); + if (!domain) { return; } + + // www appears frequently, so we truncate it + if (domain && domain.substr(0, 4) === 'www.') { + domain = domain.substring(4); + } + + const meta = { target, href, domain, rel: 'nofollow' }; + if (_decorators.length) { + _decorators.forEach(cb => cb(meta)); + } + return meta; +} + +export default function renderTopicFeaturedLink(topic) { + const meta = extractLinkMeta(topic); + if (meta) { + return `${meta.domain}`; + } else { + return ''; + } +}; + +export function topicFeaturedLinkNode(topic) { + const meta = extractLinkMeta(topic); + if (meta) { + return h('a.topic-featured-link', { + attributes: { href: meta.href, rel: meta.rel, target: meta.target } + }, meta.domain); + } +} + diff --git a/app/assets/javascripts/discourse/models/category.js.es6 b/app/assets/javascripts/discourse/models/category.js.es6 index b77a4abdf33..fc56790194d 100644 --- a/app/assets/javascripts/discourse/models/category.js.es6 +++ b/app/assets/javascripts/discourse/models/category.js.es6 @@ -169,6 +169,18 @@ const Category = RestModel.extend({ @computed("id") isUncategorizedCategory(id) { return id === Discourse.Site.currentProp("uncategorized_category_id"); + }, + + @computed('custom_fields.topic_featured_link_allowed') + topicFeaturedLinkAllowed: { + get(allowed) { + return allowed === "true"; + }, + set(value) { + value = value ? "true" : "false"; + this.set("custom_fields.topic_featured_link_allowed", value); + return value; + } } }); diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index e782c4074ae..52d8cde6aef 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -32,13 +32,15 @@ const CLOSED = 'closed', target_usernames: 'targetUsernames', typing_duration_msecs: 'typingTime', composer_open_duration_msecs: 'composerTime', - tags: 'tags' + tags: 'tags', + featured_link: 'featuredLink' }, _edit_topic_serializer = { title: 'topic.title', categoryId: 'topic.category.id', - tags: 'topic.tags' + tags: 'topic.tags', + featuredLink: 'topic.featured_link' }; const Composer = RestModel.extend({ @@ -136,6 +138,14 @@ const Composer = RestModel.extend({ canEditTitle: Em.computed.or('creatingTopic', 'creatingPrivateMessage', 'editingFirstPost'), canCategorize: Em.computed.and('canEditTitle', 'notCreatingPrivateMessage'), + @computed('canEditTitle', 'creatingPrivateMessage', 'categoryId') + canEditTopicFeaturedLink(canEditTitle, creatingPrivateMessage, categoryId) { + if (!this.siteSettings.topic_featured_link_enabled || !canEditTitle || creatingPrivateMessage) { return false; } + + const categoryIds = this.site.get('topic_featured_link_allowed_category_ids'); + return categoryIds === undefined || !categoryIds.length || categoryIds.indexOf(categoryId) !== -1; + }, + // Determine the appropriate title for this action actionTitle: function() { const topic = this.get('topic'); @@ -180,6 +190,10 @@ const Composer = RestModel.extend({ }.property('action', 'post', 'topic', 'topic.title'), + @computed('canEditTopicFeaturedLink') + showComposerEditor(canEditTopicFeaturedLink) { + return canEditTopicFeaturedLink ? !this.siteSettings.topic_featured_link_onebox : true; + }, // whether to disable the post button cantSubmitPost: function() { @@ -269,11 +283,12 @@ const Composer = RestModel.extend({ } }.property('privateMessage'), - missingReplyCharacters: function() { - const postType = this.get('post.post_type'); - if (postType === this.site.get('post_types.small_action')) { return 0; } - return this.get('minimumPostLength') - this.get('replyLength'); - }.property('minimumPostLength', 'replyLength'), + @computed('minimumPostLength', 'replyLength', 'canEditTopicFeaturedLink') + missingReplyCharacters(minimumPostLength, replyLength, canEditTopicFeaturedLink) { + if (this.get('post.post_type') === this.site.get('post_types.small_action') || + canEditTopicFeaturedLink && this.siteSettings.topic_featured_link_onebox) { return 0; } + return minimumPostLength - replyLength; + }, /** Minimum number of characters for a post body to be valid. @@ -492,6 +507,14 @@ const Composer = RestModel.extend({ save(opts) { if (!this.get('cantSubmitPost')) { + + // change category may result in some effect for topic featured link + if (this.get('canEditTopicFeaturedLink')) { + if (this.siteSettings.topic_featured_link_onebox) { this.set('reply', null); } + } else { + this.set('featuredLink', null); + } + return this.get('editingPost') ? this.editPost(opts) : this.createPost(opts); } }, @@ -512,7 +535,8 @@ const Composer = RestModel.extend({ stagedPost: false, typingTime: 0, composerOpened: null, - composerTotalOpened: 0 + composerTotalOpened: 0, + featuredLink: null }); }, diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs index 72883cba5ad..790e61e9390 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs @@ -19,6 +19,17 @@ +{{#if siteSettings.topic_featured_link_enabled}} +
+ +
+{{/if}} +