diff --git a/app/assets/javascripts/discourse/components/composer-title.js.es6 b/app/assets/javascripts/discourse/components/composer-title.js.es6 index 3d806ca0f7d..ddc6a4c4053 100644 --- a/app/assets/javascripts/discourse/components/composer-title.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-title.js.es6 @@ -1,8 +1,11 @@ -import computed from 'ember-addons/ember-computed-decorators'; +import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; import InputValidation from 'discourse/models/input-validation'; +import { load, lookupCache } from 'pretty-text/oneboxer'; +import { ajax } from 'discourse/lib/ajax'; export default Ember.Component.extend({ classNames: ['title-input'], + watchForLink: Ember.computed.alias('composer.canEditTopicFeaturedLink'), didInsertElement() { this._super(); @@ -26,5 +29,74 @@ export default Ember.Component.extend({ if (reason) { return InputValidation.create({ failed: true, reason, lastShownAt: lastValidatedAt }); } + }, + + @observes('composer.titleLength') + _titleChanged() { + if (this.get('composer.titleLength') === 0) { this.set('autoPosted', false); } + if (this.get('autoPosted') || !this.get('watchForLink')) { return; } + + if (Ember.testing) { + this._checkForUrl(); + } else { + Ember.run.debounce(this, this._checkForUrl, 500); + } + }, + + @observes('composer.replyLength') + _clearFeaturedLink() { + if (this.get('watchForLink') && this.get('composer.replyLength') === 0) { + this.set('composer.featuredLink', null); + } + }, + + _checkForUrl() { + if (this.get('isAbsoluteUrl') && (this.get('composer.reply')||"").length === 0) { + // Try to onebox. If success, update post body and title. + + this.set('composer.loading', true); + + const link = document.createElement('a'); + link.href = this.get('composer.title'); + + let loadOnebox = load(link, false, ajax); + + if (loadOnebox && loadOnebox.then) { + loadOnebox.then( () => { + this._updatePost(lookupCache(this.get('composer.title'))); + }).finally(() => { + this.set('composer.loading', false); + }); + } else { + this._updatePost(loadOnebox); + this.set('composer.loading', false); + } + } + }, + + _updatePost(html) { + if (html) { + this.set('autoPosted', true); + this.set('composer.featuredLink', this.get('composer.title')); + + const $h = $(html), + header = $h.find('h4').length > 0 ? $h.find('h4') : $h.find('h3'); + + this.set('composer.reply', this.get('composer.title')); + + if (header.length > 0 && header.text().length > 0) { + this.set('composer.title', header.text().trim()); + } else { + const filename = (this.get('composer.featuredLink')||"").split("/").pop(); + if (filename && filename.length > 0) { + this.set('composer.title', filename.trim()); + } + } + } + }, + + @computed('composer.title') + isAbsoluteUrl() { + return this.get('composer.titleLength') > 0 && /^(https?:)?\/\/[\w\.\-]+/i.test(this.get('composer.title')); } }); diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index 52d8cde6aef..eacdd96c78c 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -146,6 +146,11 @@ const Composer = RestModel.extend({ return categoryIds === undefined || !categoryIds.length || categoryIds.indexOf(categoryId) !== -1; }, + @computed('canEditTopicFeaturedLink') + titlePlaceholder() { + return this.get('canEditTopicFeaturedLink') ? 'composer.title_or_link_placeholder' : 'composer.title_placeholder'; + }, + // Determine the appropriate title for this action actionTitle: function() { const topic = this.get('topic'); diff --git a/app/assets/javascripts/discourse/templates/components/composer-title.hbs b/app/assets/javascripts/discourse/templates/components/composer-title.hbs index 1294aa29661..7da7bb6ec38 100644 --- a/app/assets/javascripts/discourse/templates/components/composer-title.hbs +++ b/app/assets/javascripts/discourse/templates/components/composer-title.hbs @@ -2,7 +2,7 @@ tabindex="2" id="reply-title" maxLength=siteSettings.max_topic_title_length - placeholderKey="composer.title_placeholder" + placeholderKey=composer.titlePlaceholder disabled=composer.loading}} {{popup-input-tip validation=validation}} diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs index c8712e360e1..1034e3a23fa 100644 --- a/app/assets/javascripts/discourse/templates/composer.hbs +++ b/app/assets/javascripts/discourse/templates/composer.hbs @@ -80,11 +80,6 @@ {{/if}} {{render "additional-composer-buttons" model}} {{/if}} - {{#if model.canEditTopicFeaturedLink}} - - {{/if}} {{/if}} {{plugin-outlet "composer-fields"}} diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index aed888a39f8..65c0f3f6a81 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -25,9 +25,6 @@ {{category-chooser valueAttribute="id" value=buffered.category_id}} {{/if}} - {{#if canEditTopicFeaturedLink}} - {{text-field type="url" value=buffered.featured_link id='topic-featured-link' placeholderKey="composer.topic_featured_link_placeholder"}} - {{/if}} {{#if canEditTags}}
{{tag-chooser tags=buffered.tags categoryId=buffered.category_id}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 4255aaa32a6..820734bf5c9 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1072,6 +1072,7 @@ en: users_placeholder: "Add a user" title_placeholder: "What is this discussion about in one brief sentence?" + title_or_link_placeholder: "Type title, or paste a link here" edit_reason_placeholder: "why are you editing?" show_edit_reason: "(add edit reason)" topic_featured_link_placeholder: "Enter link shown with title." diff --git a/test/javascripts/acceptance/composer-topic-links-test.js.es6 b/test/javascripts/acceptance/composer-topic-links-test.js.es6 new file mode 100644 index 00000000000..98dac6b8409 --- /dev/null +++ b/test/javascripts/acceptance/composer-topic-links-test.js.es6 @@ -0,0 +1,42 @@ +import { acceptance } from "helpers/qunit-helpers"; + +acceptance("Composer topic featured links", { + loggedIn: true, + settings: { + topic_featured_link_enabled: true + } +}); + + +test("onebox with title", () => { + visit("/"); + click('#create-topic'); + fillIn('#reply-title', "http://www.example.com/has-title.html"); + andThen(() => { + ok(find('.d-editor-preview').html().trim().indexOf('onebox') > 0, "it pastes the link into the body and previews it"); + ok(exists('.d-editor-textarea-wrapper .popup-tip.good'), 'the body is now good'); + equal(find('.title-input input').val(), "An interesting article", "title is from the oneboxed article"); + }); +}); + +test("onebox result doesn't include a title", () => { + visit("/"); + click('#create-topic'); + fillIn('#reply-title', 'http://www.example.com/no-title.html'); + andThen(() => { + ok(find('.d-editor-preview').html().trim().indexOf('onebox') > 0, "it pastes the link into the body and previews it"); + ok(exists('.d-editor-textarea-wrapper .popup-tip.good'), 'the body is now good'); + equal(find('.title-input input').val(), "no-title.html", "title is from the end of the url"); + }); +}); + +test("no onebox result", () => { + visit("/"); + click('#create-topic'); + fillIn('#reply-title', "http://www.example.com/nope-onebox.html"); + andThen(() => { + equal(find('.d-editor-preview').html().trim().indexOf('onebox'), -1, "link isn't put into the post"); + equal(find('.d-editor-input').val().length, 0, "link isn't put into the post"); + equal(find('.title-input input').val(), "http://www.example.com/nope-onebox.html", "title is unchanged"); + }); +}); diff --git a/test/javascripts/helpers/create-pretender.js.es6 b/test/javascripts/helpers/create-pretender.js.es6 index bfb071f90c0..fe1b4d1a294 100644 --- a/test/javascripts/helpers/create-pretender.js.es6 +++ b/test/javascripts/helpers/create-pretender.js.es6 @@ -320,6 +320,26 @@ export default function() { this.delete('/admin/users/:user_id/revoke_api_key', success); this.post('/admin/badges', success); this.delete('/admin/badges/:id', success); + + this.get('/onebox', request => { + if (request.queryParams.url === 'http://www.example.com/has-title.html') { + return [ + 200, + {"Content-Type": "application/html"}, + '' + ]; + } + + if (request.queryParams.url === 'http://www.example.com/no-title.html') { + return [ + 200, + {"Content-Type": "application/html"}, + '' + ]; + } + + return [404, {"Content-Type": "application/html"}, ''];; + }); }); server.prepareBody = function(body){ diff --git a/test/javascripts/models/composer-test.js.es6 b/test/javascripts/models/composer-test.js.es6 index 1ed5a691c37..aa400397eba 100644 --- a/test/javascripts/models/composer-test.js.es6 +++ b/test/javascripts/models/composer-test.js.es6 @@ -231,3 +231,19 @@ test("Title length for static page topics as admin", function() { composer.set('title', ''); ok(!composer.get('titleLengthValid'), "admins must set title to at least 1 character"); }); + +test("title placeholder depends on what you're doing", function() { + let composer = createComposer({action: Composer.CREATE_TOPIC}); + equal(composer.get('titlePlaceholder'), 'composer.title_placeholder', "placeholder for normal topic"); + + composer = createComposer({action: Composer.PRIVATE_MESSAGE}); + equal(composer.get('titlePlaceholder'), 'composer.title_placeholder', "placeholder for private message"); + + Discourse.SiteSettings.topic_featured_link_enabled = true; + + composer = createComposer({action: Composer.CREATE_TOPIC}); + equal(composer.get('titlePlaceholder'), 'composer.title_or_link_placeholder', "placeholder invites you to paste a link"); + + composer = createComposer({action: Composer.PRIVATE_MESSAGE}); + equal(composer.get('titlePlaceholder'), 'composer.title_placeholder', "placeholder for private message with topic links enabled"); +});