User script: drag & drop from </> to [quote]...



  • As we all know, highlighting text to <blockquote> it works... kind of. As long as you don't care about any formatting, images, ... anything except the plain text and built-in emojis (which they actually got to quote correctly... shocking, I know). Quoting the whole post does work, but then you have to trim it down to the part you wanted to quote. Overall, this kind of sucks. Why can't "quote reply" just work?

    So, I started wondering if I could fix the "quote reply", but of course it's all Ember and who the hell knows how it does anything. Trying to set a breakpoint on clicking that just stopped somewhere in the middle of a huge-ass 32k-character-long line in the jQuery script, which Prettify Source, naturally, didn't help.

    Then it occurred to me that if you could highlight text from the raw and quote that into the reply box, then it ought to work correctly. But the "quote reply" doesn't show up when you highlight text in the raw. Then I thought highlighting the raw text and drag-and-drop to the reply box would work, but Discourse manages to make it impossible to drag-and-drop anything, anywhere, anyway. So... I wrote a script that lets you do it.

    Without further ado:

    document.body.addEventListener("mousedown", function (e) {
      function dragStart(ev) {
        e.target.removeEventListener("dragstart", dragStart);
        
        for (var n = e.target.parentElement; !n.hasAttribute("data-post-id"); n = n.parentElement);
        
        // make sure the reply box is open
        var r = document.getElementById("reply-control")
        if (!r || r.classList.contains("closed")) {
          n.querySelector(".fa-reply").parentElement.click();
        }
    
        document.getElementById("reply-control").addEventListener("drop", drop);
        
        // add BBcode [quote] tags to the quoted text, and set it as the data for the drag & drop event
        ev.dataTransfer.setData("text/bbcode", "[quote=\"" + [n.querySelector(".username a").getAttribute("data-user-card"),
          n.id.replace("_", ":"), "topic:" + document.getElementById("topic").getAttribute("data-topic-id")].join(", ") +
          (t.textContent == s.toString() ? ", full:true" : "") + "\"]\n" + s.toString() + "\n</blockquote>\n\n");
        ev.dataTransfer.setData("text/plain", s.toString());
      }
      
      function drop(ev) {
        if (ev.dataTransfer.types.contains("text/bbcode")) {
          var i = ev.target.selectionStart, t = ev.dataTransfer.getData("text/BBcode");
          ev.target.value = ev.target.value.slice(0, i) + t + ev.target.value.slice(i);
          ev.target.selectionStart = ev.target.selectionEnd = i + t.length;
          ev.stopImmediatePropagation(); // stops this handler from running more than once
          ev.preventDefault(); // stops the default handler from inserting the event's text/plain data
        }
    
        // there's apparently a glitch where the textarea has focus, but is hiding it - blur and refocus fixes this
        ev.target.blur();
        
        // focus() won't do anything unless the drop event's allowed to finish first, so use setTimeout to schedule it next
        setTimeout(() => ev.target.focus(), 0);
      }
      
      function dragEnd(ev) {
        ev.target.removeEventListener("dragend", dragEnd);
        document.getElementById("reply-control").removeEventListener("drop", drop);
        
        // if the dragged text wasn't dropped into the reply box (and the reply box contains nothing else), cancel the reply
        var r = document.getElementById("reply-control");
        if (r && !(r.querySelector("textarea") || {}).value && r.querySelector(".cancel"))
          r.querySelector(".cancel").click();
      }
      
      // these 3 if statements check that the selection contains one non-empty range which is contained entirely within a raw block
      var s = window.getSelection(), t = e.target;
      if (!e.ctrlKey && s.rangeCount == 1) {
        s = s.getRangeAt(0);
        
        // just in case the original target element wasn't the raw block itself, but an element within that block
        // Discourse wouldn't do this, but other userscripts *might* interfere...
        while (t.matches(".tdwtf-raw-area *")) t = t.parentElement;
        
        // if the source of the mouseDown event was a raw area which completely contains some highlighted text
        if (t.matches(".tdwtf-raw-area") && t.contains(s.startContainer) && t.contains(s.endContainer)) {
          if (s.startContainer != s.endContainer || s.startOffset != s.endOffset) {
            // don't let higher-up event handlers run - apparently something up there prevents default so you can't start dragging
            e.stopPropagation();
            
            e.target.addEventListener("dragstart", dragStart);
            e.target.addEventListener("dragend", dragEnd);
          }
        }
      }
    });
    

    To use:

    • click the </> button for the post to which you'll reply
    • highlight as much of the raw text as you wish to quote (some or all)
    • start dragging it (if you don't have the reply box open, it'll open)
    • drop it into the reply box

    The correct BBcode [quote] tags will be added to format it as a reply. It will not be escaped, so it should Just Work, with the exception of :disco:-format stupidity where certain things get formatted totally differently because they're inside of a [quote].


  • Notification Spam Recipient

    @anotherusername said:

    The correct BBcode <blockquote> tags will be added t

    Noice![quote="anotherusername, post:1, topic:54580"]
    entListener("dragend", dragEnd);
    @anotherusername said:
    To use:

    • click the </> button for the post to which you'll reply
    • highlight as much of the raw text as you wish to quote (some or all)
    • start dragging it (if you don't have the reply box open, it'll open)
    • drop it into the reply box


  • Okay so I realized that dragging text might not ultimately end up in the reply box, so I changed the data type to text/bbcode and changed the drop handler to insert that instead of the text/plain data. The event still transfers text/plain data, so for example if you highlighted a URL to drag up to the browser's interface, it won't have the BBcode tags attached to it when you drop it there.

    That revealed a minor bug I had where the drop event handler wasn't being attached to the reply box in some cases, and another bug where the drop event executed multiple times (and thus inserted the [quote] multiple times). So both of those had to be fixed, too, but it's working.



  • I made an alternative method as a bookmarklet:

    javascript:$(document).on('mouseover', '.tdwtf-raw-area:not(.cooked)', function(e) { $(e.target).addClass('cooked'); });
    

    To use: activate the bookmarklet once per Discourse session, then use highlight-quoting inside the raw text as you would in cooked.

    [quote="anotherusername, post:1, topic:54580"]
    As we all know, highlighting text to <blockquote> it works... kind of.

    This could potentially break anything that relies on tdwtf-raw-area not being cooked but otherwise seems to work ok. It could also be done server-side by adding the cooked class to raw (@PJH) which would make the bookmarklet redundant.

    : It's not quite perfect as it escapes <, >, etc.



  • Close, but not quite...

    results in...

    @anotherusername said:

    click the <tt></></tt> button

    instead of...

    @anotherusername said:

    click the </> button

    I originally considered whether it'd be possible to just munge the selection in the cooked post to add all teh codez before Discourse grabbed the selection, but I ran into that same problem there... < and > get escaped, so there's no way to convince it to quote HTML tags correctly.



  • Yeah, I noticed that after posting it. It's because the contents of the raw block are escaped and Discourse's native select-quoting copies it as-is. Although, curiously enough, & appears to be un-escaped.

    @hungrier said:

    escapes <, >, etc.

    Of course none of this would be necessary if the original Discofeature did

    @anotherusername said:

    munge the selection in the cooked post to add all teh codez

    so we wouldn't need to mess with the raw at all.



  • @hungrier said:

    Of course none of this would be necessary if the original Discofeature

    ...just worked correctly. Yes...


Log in to reply