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}} +