4
\$\begingroup\$

I sometimes don't like to look at images in chat. When they're just a URL then I'm fine, as then I have the choice to look at the image or not, and blends in with the other text. Therefore, I decided to make a onebox hider for chat. It hides the onebox, and adds onebox: <link> before the onebox, with the ability to click the 'onebox' text, to show or hide the onebox.

There, however, are some allowed oneboxes such as SE blog posts, SE post and Twitter links. This is as they don't normally have images, which I think is ok. These are stored in allowed_oneboxes, so that if another user wants, they can add or remove allowed oneboxes with ease.

I looked through "Desktop Notifications for flags and queue items" and added some of the recommendations as I had problems with jQuery and wanted to make sure my environment was at least somewhat well setup.

When I started to write this question I was also notified about Badge Oneboxer for Chat.SE, where I found out about MutationObserver, which was better than what I was doing with setInterval.

I've tried to make this as clean as possible. However, as I've not done too much with Greasemonkey and JavaScript, I'd like to make sure I've kept everything clean and correct.

/** @preserve
// ==UserScript==
// @name        Onebox Hider
// @namespace   
// @description Hides oneboxes from SE chat.
// @author      Peilonrayz
// @version     0.0.1
// @copyright   2017, Peilonrayz
// @license     MIT; https://opensource.org/licenses/MIT
// @require     https://code.jquery.com/jquery-3.2.1.min.js
// @match       *://chat.stackexchange.com/*
// @match       *://chat.stackoverflow.com/rooms/*
// @match       *://chat.meta.stackexchange.com/rooms/*
// @grant       none
// ==/UserScript==
*/

// Copyright 2017 Peilonrayz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

"use strict";

(function (window, undefined) {
window.$.noConflict();
(function ($) {


var allowed_oneboxes = ['ob-post', 'ob-blog', 'ob-tweet'];

function change_onebox(onebox) {
    var href = onebox.find('a')[0].href;
    var show = onebox
        .hide()
        .before('<div class="onebox-toggle"><span>onebox:</span> <a href="'+href+'">'+href+'</a></div>')
        .prev()
        .find('span')
        .click(() => onebox.toggle());
    return onebox;
}

function flatten(arrayOfArrays) {
    return Array.concat.apply([], arrayOfArrays);
}

function toArray(it) {
    return Array.slice(it);
}

function update_oneboxes(mutations) {
    var msgs;
    if (mutations == undefined) {
        msgs = $('.message .content .onebox');
    } else {
        msgs = $(flatten(mutations.map(m => toArray(m.addedNodes))))
            .find('.onebox');
    }
    msgs.map((i, onebox) => $(onebox))
        .filter((i, onebox) => !onebox.prev().hasClass('onebox-toggle'))
        .filter((i, onebox) => !allowed_oneboxes.some(c => onebox.hasClass(c)))
        .map((i, onebox) => change_onebox(onebox));
}

new MutationObserver(update_oneboxes)
    .observe(document.getElementById('chat'), {childList: true});


})(window.jQuery);
})(Function("return this")());
\$\endgroup\$
  • 1
    \$\begingroup\$ You don't need to include jQuery in a userscript for an SE site as they already have jQuery. \$\endgroup\$ – I wrestled a bear once. Jun 28 '17 at 20:16
  • \$\begingroup\$ @Iwrestledabearonce. Greasemonkey sandboxes the user-script, so I have to include it to be able to use it. \$\endgroup\$ – Peilonrayz Jun 28 '17 at 21:04
  • 1
    \$\begingroup\$ Yeah that means you can't introduce new variables to the global scope it doesn't mean you can't use vars that already exist like jQ. I've written dozens of userscripts for SE on both greasemonkey and Tampermonkey and I've never included jQuery. Trust me, you don't need it. \$\endgroup\$ – I wrestled a bear once. Jun 28 '17 at 21:08
  • \$\begingroup\$ Does Array.concat.apply work? You should use Array.prototype.concat.apply or [].concat.apply. \$\endgroup\$ – Tushar Jul 3 '17 at 5:29
  • \$\begingroup\$ @Tushar On Firefox latest it does. Otherwise this this question would be off-topic here :) \$\endgroup\$ – Peilonrayz Jul 3 '17 at 7:26
2
+50
\$\begingroup\$

For the most part it looks good, and there are some nice touches which I'll have to remember to look up the next time I write JS, but there are a couple of things which don't feel quite right.


    var show = onebox
        .hide()
        .before('<div class="onebox-toggle"><span>onebox:</span> <a href="'+href+'">'+href+'</a></div>')
        .prev()
        .find('span')
        .click(() => onebox.toggle());

The var show = is completely unnecessary, because show isn't used. But more importantly, I don't like creating an object (the span element), losing it, and having to find it. I concede that this is a matter of style, but I'd prefer to create the span with its handler and then insert it into the div:

    var span = $('<span>onebox:</span>').click(() => onebox.toggle());
    var div = $('<div class="onebox-toggle"> <a href="'+href+'">'+href+'</a></div>')
        .prepend(span);
    onebox.hide().before(div);

or (IMO uglier)

    onebox.hide().before(
        $('<div class="onebox-toggle"> <a href="'+href+'">'+href+'</a></div>')
            .prepend(
                $('<span>onebox:</span>').click(() => onebox.toggle())
            )
    );

Compare

        msgs = $(flatten(mutations.map(m => toArray(m.addedNodes))))

to

    msgs.map((i, onebox) => $(onebox))
        .filter((i, onebox) => !onebox.prev().hasClass('onebox-toggle'))
        .filter((i, onebox) => !allowed_oneboxes.some(c => onebox.hasClass(c)))
        .map((i, onebox) => change_onebox(onebox));

According to the usually excellent MDN, map and filter take (currentValue, index, array). This is consistent with the first excerpt, but not with the second.

The explanation is that the second one is jQuery's map and filter, but that's a nasty trap. I'm partial to the convention that variables which hold a jQuery object should have names starting $. Hungarian notation may be maligned in some contexts, but it's valuable in languages which don't do static typing.

\$\endgroup\$
  • \$\begingroup\$ Would you mind showing me how you'd create the span? I hadn't thought of doing it that way. And since it's "a matter of style", it'd be nice to see both ways, :) I agree with JQuery's map being annoyingly different from the native map, I'll give Hungarian notation a try, however opposed I am normally to it, :) \$\endgroup\$ – Peilonrayz Jul 3 '17 at 10:31
  • \$\begingroup\$ @Peilonrayz, added. \$\endgroup\$ – Peter Taylor Jul 3 '17 at 10:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.