diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 3f07c7b2f40..78731bef8fe 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -9,6 +9,8 @@ import { emojiUrlFor } from 'discourse/lib/text'; import { getRegister } from 'discourse-common/lib/get-owner'; import { findRawTemplate } from 'discourse/lib/raw-templates'; import { determinePostReplaceSelection, clipboardData } from 'discourse/lib/utilities'; +import { ajax } from 'discourse/lib/ajax'; +import { popupAjaxError } from 'discourse/lib/ajax-error'; import deprecated from 'discourse-common/lib/deprecated'; // Our head can be a static string or a function that returns a string @@ -616,7 +618,7 @@ export default Ember.Component.extend({ Ember.run.scheduleOnce("afterRender", () => $textarea.focus()); }, - _detectTable(text) { + _extractTable(text) { if (text.endsWith("\n")) { text = text.substring(0, text.length - 1); } @@ -639,21 +641,43 @@ export default Ember.Component.extend({ paste(e) { const clipboard = clipboardData(e); - const types = clipboard.types; - let preventDefault = false; + const placeholder = `${ I18n.t('pasting') }`; + let plainText = clipboard.getData("text/plain"); + const html = clipboard.getData("text/html"); + let handled = false; - if (types.some(t => t === "text/plain")) { - const text = clipboard.getData("text/plain"); - const table = this._detectTable(text); + if (plainText) { + plainText = plainText.trim(); + const table = this._extractTable(plainText); if (table) { this._addText(this._getSelected(), table); - preventDefault = true; + handled = true; } - } else if (types.some(t => t === "Files")) { - preventDefault = true; } - if (preventDefault) { + if (html && !handled) { + const self = this; + + this.appEvents.trigger('composer:insert-text', placeholder); + handled = true; + + ajax('/composer/parse_html', { + type: 'POST', + data: { html } + }).then(response => { + self.appEvents.trigger('composer:replace-text', placeholder, response.markdown); + }).catch(error => { + if (plainText) { + self.appEvents.trigger('composer:replace-text', placeholder, plainText); + } else { + popupAjaxError(error); + } + }); + } + + const uploadFiles = clipboard.types.includes("Files") && !plainText && !html; + + if (handled || uploadFiles) { e.preventDefault(); } }, diff --git a/app/controllers/composer_controller.rb b/app/controllers/composer_controller.rb new file mode 100644 index 00000000000..6eb78a5d061 --- /dev/null +++ b/app/controllers/composer_controller.rb @@ -0,0 +1,12 @@ +require_dependency 'html_to_markdown' + +class ComposerController < ApplicationController + + before_action :ensure_logged_in + + def parse_html + markdown_text = HtmlToMarkdown.new(params[:html]).to_markdown + + render json: { markdown: markdown_text } + end +end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 67e35a9cb2b..4662366d966 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -309,6 +309,8 @@ en: uploading_filename: "Uploading {{filename}}..." uploaded: "Uploaded!" + pasting: "Pasting..." + enable: "Enable" disable: "Disable" undo: "Undo" diff --git a/config/routes.rb b/config/routes.rb index 394ad7d9bde..c66d91e15e4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -301,6 +301,7 @@ Discourse::Application.routes.draw do get "session/current" => "session#current" get "session/csrf" => "session#csrf" get "composer_messages" => "composer_messages#index" + post "composer/parse_html" => "composer#parse_html" resources :static post "login" => "static#enter", constraints: { format: /(json|html)/ } diff --git a/spec/requests/composer_controller_spec.rb b/spec/requests/composer_controller_spec.rb new file mode 100644 index 00000000000..2a8923c37b2 --- /dev/null +++ b/spec/requests/composer_controller_spec.rb @@ -0,0 +1,26 @@ +require 'rails_helper' + +RSpec.describe ComposerController do + let(:user) { Fabricate(:user) } + + describe '#parse_html' do + + it "should not be able access without sign in" do + expect { + post "/composer/parse_html.json", params: { + html: "hello" + } + }.to raise_error(Discourse::NotLoggedIn) + end + + it "should convert html tags to markdown text" do + sign_in(user) + + post "/composer/parse_html.json", params: { + html: "hello" + } + + expect(response.body).to eq("{\"markdown\":\"**hello**\"}") + end + end +end