Skip to content Skip to sidebar Skip to footer

How Does Twitter Implement Its Tweet Box?

I'm trying to implement something like Twitter's tweet box, specifically: Automatically highlights text in a red background when the overall length exceeds 140 characters. Automat

Solution 1:

What I did is kind of a hack but works quite well as a workaround solution.

I've got a simple textarea (not contenteditable because of what's next, I'll explain below) and a div which is absolute positioned behind the textarea. Using javascript, I copy the content from the textarea to the div while splitting it at 140 chars and putting all extra characters inside an <em /> tag.

Well, it's a little bit more complicated because twitter algorithm to compute a tweet length is different (links doesn't count as their real value, because of t.co url shortening). The exact method can be found as part of the official twitter/twitter-txt repository.

Step-by-step code.

Wrap a simple textarea into a div to simplify the css:

<div class="tweet-composer">
  <textarea class="editor-textarea js-keeper-editor">This is some text that will be highlight when longer than 20 characters. Like twitter. Type something...</textarea>
  <div class="js-keeper-placeholder-back"></div>
</div>

CSS is just making the textarea and the div right above each other and highlight the text.

.tweet-composer {
  position: relative;
  z-index: 1;
}

.js-keeper-editor,
.js-keeper-placeholder-back {
  background: transparent;
  border: 1px solid #eee;
  font-family: Helvetica, Arial, sans-serif;
  font-size: 14px; /* Same font for both. */margin: auto;
  min-height: 200px;
  outline: none;
  padding: 10px;
  width: 100%;
}

.js-keeper-placeholder-back {
  bottom: 0;
  color: transparent;
  left: 0;
  position: absolute;
  top: 0;
  white-space: pre-wrap;
  width: 100%;
  word-wrap: break-word;
  z-index: -1;
}

.js-keeper-placeholder-backem {
  background: #fcc!important;
}

Now the fun part, this is implemented using jQuery but that's not the important thing.

if (0 > remainingLength) {
  // Split value if greater than var allowedValuePart = currentValue.slice(0, realLength),
      refusedValuePart = currentValue.slice(realLength)
  ;

  // Fill the hidden div.$placeholderBacker.html(allowedValuePart + '<em>' + refusedValuePart + '</em>');
} else {
  $placeholderBacker.html('');
}

Add some event handler on change, and te common document ready and you're done. See the codepen link below.

Note that the div placed behind can be created using js too, when loading the page:

// Create a pseudo-element that will be hidden behind the placeholder.var$placeholderBacker= $('<div class="js-keeper-placeholder-back"></div>');
$placeholderBacker.insertAfter($textarea);

Full example

See working example over here:http://codepen.io/hussard/pen/EZvaBZ

Solution 2:

This is not a direct answer with source code building the part of your application to your spec.

This is really not an easy thing to do.

You're right - the way to solving this problem is to use a contenteditable="true" container. But I'm afraid that from there it gets much much more complicated.

Enter DraftJS - A javascript solution to rich-text editing which does most of the heavy lifting for you.

Both of my solutions below require the use of both React and DraftJS


The Plug-&-Play Answer :

The React + DraftJS + Someone Else's "Plugin" solution is already here. You can check out draft-js-plugins.com. And here's the Github source code.

Personally, I wouldn't go this way. I would rather study their GitHub source code and implement my own DraftJS entities. Because I think that React-JS-Plugins feels a little bit clunky and overweight for just Links and Mentions. Plus, where are you "mentioning" from? Your own Application? Twitter? Doing it this way lets you have control over where you're grabbing the so-called "mentions".


The DIY Solution:

The best way I have found is to build your own set of working entities on top of a DraftJS based rich-text editor.

This of course also requires that you're using React.

Forgive me for not actually building a complete set of working code. I was interested in doing something similar for an App I'm building in Meteor with React on the front-end. So this really made sense to me because I'd only be adding one more library (DraftJS) and the rest would be my custom entities coded out.

Solution 3:

Turns out this is really not an easy thing to do. I've been struggling with it for the past few days, and I'm not very close to a solution.

Your best drop-in solution currently is the At.js library, which is still being maintained, but definitely isn't perfect. One of the examples shows how you can kind of do a text highlight.

The most annoying part of this problem is that Twitter has a beautiful solution that seemingly works perfectly staring us right in the face. I took some time investigating the way they implement their "tweet box," and it's definitely not trivial. It looks like they're doing almost everything manually, including emulating undo/redo functionality, intercepting copy/pastes, providing custom code for IE/W3C, custom-coding Mac/PC, and more. They use a contenteditable div, which is in-and-of-itself problematic due to differences in browser implementations. It's pretty impressive, actually.

Here's the most relevant (obfuscated, unfortunately) code, taken from Twitter's boot JavaScript file (found by inspecting the header of the logged-in Twitter homepage). I didn't want to directly copy and paste the link, in case it's personalized to my Twitter account.

define("app/utils/html_text", ["module", "require", "exports"], function(module, require, exports) {
    function isTextNode(a) {
        return a.nodeType == 3 || a.nodeType == 4
    }

    function isElementNode(a) {
        return a.nodeType == 1
    }

    function isBrNode(a) {
        return isElementNode(a) && a.nodeName.toLowerCase() == "br"
    }

    function isOutsideContainer(a, b) {
        while (a !== b) {
            if (!a) return !0;
            a = a.parentNode
        }
    }
    var useW3CRange = window.getSelection,
        useMsftTextRange = !useW3CRange && document.selection,
        useIeHtmlFix = navigator.appName == "Microsoft Internet Explorer",
        NBSP_REGEX = /[\xa0\n\t]/g,
        CRLF_REGEX = /\r\n/g,
        LINES_REGEX = /(.*?)\n/g,
        SP_LEADING_OR_FOLLOWING_CLOSE_TAG_OR_PRECEDING_A_SP_REGEX = /^ |(<\/[^>]+>) | (?= )/g,
        SP_LEADING_OR_TRAILING_OR_FOLLOWING_A_SP_REGEX = /^ | $|( ) /g,
        MAX_OFFSET = Number.MAX_VALUE,
        htmlText = function(a, b) {
            function c(a, c) {
                function h(a) {
                    var i = d.length;
                    if (isTextNode(a)) {
                        var j = a.nodeValue.replace(NBSP_REGEX, " "),
                            k = j.length;
                        k && (d += j, e = !0), c(a, !0, 0, i, i + k)
                    } elseif (isElementNode(a)) {
                        c(a, !1, 0, i, i);
                        if (isBrNode(a)) a == f ? g = !0 : (d += "\n", e = !1);
                        else {
                            var l = a.currentStyle || window.getComputedStyle(a, ""),
                                m = l.display == "block";
                            m && b.msie && (e = !0);
                            for (var n = a.firstChild, o = 1; n; n = n.nextSibling, o++) {
                                h(n);
                                if (g) return;
                                i = d.length, c(a, !1, o, i, i)
                            }
                            g || a == f ? g = !0 : m && e && (d += "\n", e = !1)
                        }
                    }
                }
                var d = "",
                    e, f, g;
                for (var i = a; i && isElementNode(i); i = i.lastChild) f = i;
                return h(a), d
            }

            function d(a, b) {
                var d = null,
                    e = b.length - 1;
                if (useW3CRange) {
                    var f = b.map(function() {
                            return {}
                        }),
                        g;
                    c(a, function(a, c, d, h, i) {
                        g || f.forEach(function(f, j) {
                            var k = b[j];
                            h <= k && !isBrNode(a) && (f.node = a, f.offset = c ? Math.min(k, i) - h : d, g = c && j == e && i >= k)
                        })
                    }), f[0].node && f[e].node && (d = document.createRange(), d.setStart(f[0].node, f[0].offset), d.setEnd(f[e].node, f[e].offset))
                } elseif (useMsftTextRange) {
                    var h = document.body.createTextRange();
                    h.moveToElementText(a), d = h.duplicate();
                    if (b[0] == MAX_OFFSET) d.setEndPoint("StartToEnd", h);
                    else {
                        d.move("character", b[0]);
                        var i = e && b[1] - b[0];
                        i > 0 && d.moveEnd("character", i), h.inRange(d) || d.setEndPoint("EndToEnd", h)
                    }
                }
                return d
            }

            function e() {
                return document.body.contains(a)
            }

            function f(b) {
                a.innerHTML = b;
                if (useIeHtmlFix)
                    for (var c = a.firstChild; c; c = c.nextSibling) isElementNode(c) && c.nodeName.toLowerCase() == "p" && c.innerHTML == "" && (c.innerText = "")
            }

            function g(a, b) {
                return a.map(function(a) {
                    return Math.min(a, b.length)
                })
            }

            function h() {
                var b = getSelection();
                if (b.rangeCount !== 1) returnnull;
                var d = b.getRangeAt(0);
                if (isOutsideContainer(d.commonAncestorContainer, a)) returnnull;
                var e = [{
                    node: d.startContainer,
                    offset: d.startOffset
                }];
                d.collapsed || e.push({
                    node: d.endContainer,
                    offset: d.endOffset
                });
                var f = e.map(function() {
                        return MAX_OFFSET
                    }),
                    h = c(a, function(a, b, c, d) {
                        e.forEach(function(e, g) {
                            f[g] == MAX_OFFSET && a == e.node && (b || c == e.offset) && (f[g] = d + (b ? e.offset : 0))
                        })
                    });
                return g(f, h)
            }

            function i() {
                var b = document.selection.createRange();
                if (isOutsideContainer(b.parentElement(), a)) returnnull;
                var d = ["Start"];
                b.compareEndPoints("StartToEnd", b) && d.push("End");
                var e = d.map(function() {
                        return MAX_OFFSET
                    }),
                    f = document.body.createTextRange(),
                    h = c(a, function(c, g, h, i) {
                        function j(a, c) {
                            if (e[c] < MAX_OFFSET) return;
                            var d = f.compareEndPoints("StartTo" + a, b);
                            if (d > 0) return;
                            var g = f.compareEndPoints("EndTo" + a, b);
                            if (g < 0) return;
                            var h = f.duplicate();
                            h.setEndPoint("EndTo" + a, b), e[c] = i + h.text.length, c && !g && e[c]++
                        }!g && !h && c != a && (f.moveToElementText(c), d.forEach(j))
                    });
                return g(e, h)
            }
            return {
                getHtml: function() {
                    if (useIeHtmlFix) {
                        var b = "",
                            c = document.createElement("div");
                        for (var d = a.firstChild; d; d = d.nextSibling) isTextNode(d) ? (c.innerText = d.nodeValue, b += c.innerHTML) : b += d.outerHTML.replace(CRLF_REGEX, "");
                        return b
                    }
                    return a.innerHTML
                },
                setHtml: function(a) {
                    f(a)
                },
                getText: function() {
                    return c(a, function() {})
                },
                setTextWithMarkup: function(a) {
                    f((a + "\n").replace(LINES_REGEX, function(a, c) {
                        return b.mozilla || b.msie ? (c = c.replace(SP_LEADING_OR_FOLLOWING_CLOSE_TAG_OR_PRECEDING_A_SP_REGEX, "$1&nbsp;"), b.mozilla ? c + "<BR>" : "<P>" + c + "</P>") : (c = (c || "<br>").replace(SP_LEADING_OR_TRAILING_OR_FOLLOWING_A_SP_REGEX, "$1&nbsp;"), b.opera ? "<p>" + c + "</p>" : "<div>" + c + "</div>")
                    }))
                },
                getSelectionOffsets: function() {
                    var a = null;
                    return e() && (useW3CRange ? a = h() : useMsftTextRange && (a = i())), a
                },
                setSelectionOffsets: function(b) {
                    if (b && e()) {
                        var c = d(a, b);
                        if (c)
                            if (useW3CRange) {
                                var f = window.getSelection();
                                f.removeAllRanges(), f.addRange(c)
                            } else useMsftTextRange && c.select()
                    }
                },
                emphasizeText: function(b) {
                    var f = [];
                    b && b.length > 1 && e() && (c(a, function(a, c, d, e, g) {
                        if (c) {
                            var h = Math.max(e, b[0]),
                                i = Math.min(g, b[1]);
                            i > h && f.push([h, i])
                        }
                    }), f.forEach(function(b) {
                        var c = d(a, b);
                        c && (useW3CRange ? c.surroundContents(document.createElement("em")) : useMsftTextRange && c.execCommand("italic", !1, null))
                    }))
                }
            }
        };
    module.exports = htmlText
});








define("app/utils/tweet_helper", ["module", "require", "exports", "lib/twitter-text", "core/utils", "app/data/user_info"], function(module, require, exports) {
    var twitterText = require("lib/twitter-text"),
        utils = require("core/utils"),
        userInfo = require("app/data/user_info"),
        VALID_PROTOCOL_PREFIX_REGEX = /^https?:\/\//i,
        tweetHelper = {
            extractMentionsForReply: function(a, b) {
                var c = a.attr("data-screen-name"),
                    d = a.attr("data-retweeter"),
                    e = a.attr("data-mentions") ? a.attr("data-mentions").split(" ") : [],
                    f = a.attr("data-tagged") ? a.attr("data-tagged").split(" ") : [];
                e = e.concat(f);
                var g = [c, b, d];
                return e = e.filter(function(a) {
                    return g.indexOf(a) < 0
                }), d && d != c && d != b && e.unshift(d), (!e.length || c != b) && e.unshift(c), e
            },
            linkify: function(a, b) {
                return b = utils.merge({
                    hashtagClass: "twitter-hashtag pretty-link",
                    hashtagUrlBase: "/search?q=%23",
                    symbolTag: "s",
                    textWithSymbolTag: "b",
                    cashtagClass: "twitter-cashtag pretty-link",
                    cashtagUrlBase: "/search?q=%24",
                    usernameClass: "twitter-atreply pretty-link",
                    usernameUrlBase: "/",
                    usernameIncludeSymbol: !0,
                    listClass: "twitter-listname pretty-link",
                    urlClass: "twitter-timeline-link",
                    urlTarget: "_blank",
                    suppressNoFollow: !0,
                    htmlEscapeNonEntities: !0
                }, b || {}), twitterText.autoLinkEntities(a, twitterText.extractEntitiesWithIndices(a), b)
            }
        };
    module.exports = tweetHelper
});










define("app/ui/compose/with_rich_editor", ["module", "require", "exports", "app/utils/file", "app/utils/html_text", "app/utils/tweet_helper", "lib/twitter-text"], function(module, require, exports) {
    function withRichEditor() {
        this.defaultAttrs({
            richSelector: "div.rich-editor",
            linksSelector: "a",
            normalizerSelector: "div.rich-normalizer",
            $browser: $.browser
        }), this.linkify = function(a) {
            var b = {
                urlTarget: null,
                textWithSymbolTag: RENDER_URLS_AS_PRETTY_LINKS ? "b" : "",
                linkAttributeBlock: function(a, b) {
                    var c = a.screenName || a.url;
                    c && (this.urlAndMentionsCharCount += c.length + 2), delete b.title, delete b["data-screen-name"], b.dir = a.hashtag && this.shouldBeRTL(a.hashtag, 0) ? "rtl" : "ltr", b.role = "presentation"
                }.bind(this)
            };
            returnthis.urlAndMentionsCharCount = 0, tweetHelper.linkify(a, b)
        }, this.around("setSelection", function(a, b) {
            b && this.setSelectionIfFocused(b)
        }), this.around("setCursorPosition", function(a, b) {
            b === undefined && (b = this.attr.cursorPosition), b === undefined && (b = MAX_OFFSET), this.setSelectionIfFocused([b])
        }), this.around("detectUpdatedText", function(a, b, c) {
            var d = this.htmlRich.getHtml(),
                e = this.htmlRich.getSelectionOffsets() || [MAX_OFFSET],
                f = c !== undefined;
            if (d === this.prevHtml && e[0] === this.prevSelectionOffset && !b && !f) return;
            var g = f ? c : this.htmlRich.getText(),
                h = g.replace(INVALID_CHARS_REGEX, "");
            (f || !(!d && !this.hasFocus() || this.$text.attr("data-in-composition"))) && this.reformatHtml(h, d, e, f);
            if (b || this.cleanedText != h || this.prevSelectionOffset != e[0]) this.prevSelectionOffset = e[0], this.updateCleanedTextAndOffset(h, e[0])
        }), this.reformatHtml = function(a, b, c, d) {
            this.htmlNormalizer.setTextWithMarkup(this.linkify(a)), this.interceptDataImageInContent();
            var e = this.shouldBeRTL(a, this.urlAndMentionsCharCount);
            this.$text.attr("dir", e ? "rtl" : "ltr"), this.$normalizer.find(e ? "[dir=rtl]" : "[dir=ltr]").removeAttr("dir"), RENDER_URLS_AS_PRETTY_LINKS && this.$normalizer.find(".twitter-timeline-link").wrapInner("<b>").addClass("pretty-link");
            var f = this.getMaxLengthOffset(a);
            f >= 0 && (this.htmlNormalizer.emphasizeText([f, MAX_OFFSET]), this.$normalizer.find("em").each(function() {
                this.innerHTML = this.innerHTML.replace(TRAILING_SINGLE_SPACE_REGEX, "Â ")
            }));
            var g = this.htmlNormalizer.getHtml();
            if (g !== b) {
                var h = d && !this.isFocusing && this.hasFocus();
                h && this.$text.addClass("fake-focus").blur(), this.htmlRich.setHtml(g), h && this.$text.focus().removeClass("fake-focus"), this.setSelectionIfFocused(c)
            }
            this.prevHtml = g
        }, this.interceptDataImageInContent = function() {
            if (!this.triggerGotImageData) return;
            this.$text.find("img").filter(function(a, b) {
                return b.src.match(/^data:/)
            }).first().each(function(a, b) {
                var c = file.getBlobFromDataUri(b.src);
                file.getFileData("pasted.png", c).then(this.triggerGotImageData.bind(this))
            }.bind(this))
        }, this.getMaxLengthOffset = function(a) {
            var b = this.getLength(a),
                c = this.attr.maxLength;
            if (b <= c) return -1;
            c += twitterText.getUnicodeTextLength(a) - b;
            var d = [{
                indices: [c, c]
            }];
            return twitterText.modifyIndicesFromUnicodeToUTF16(a, d), d[0].indices[0]
        }, this.setSelectionIfFocused = function(a) {
            this.hasFocus() ? (this.previousSelection = null, this.htmlRich.setSelectionOffsets(a)) : this.previousSelection = a
        }, this.selectPrevCharOnBackspace = function(a) {
            if (a.which == 8 && !a.ctrlKey) {
                var b = this.htmlRich.getSelectionOffsets();
                b && b[0] != MAX_OFFSET && b.length == 1 && (b[0] ? this.setSelectionIfFocused([b[0] - 1, b[0]]) : this.stopEvent(a))
            }
        }, this.emulateCommandArrow = function(a) {
            if (a.metaKey && !a.shiftKey && (a.which == 37 || a.which == 39)) {
                var b = a.which == 37;
                this.htmlRich.setSelectionOffsets([b ? 0 : MAX_OFFSET]), this.$text.scrollTop(b ? 0 : this.$text[0].scrollHeight), this.stopEvent(a)
            }
        }, this.stopEvent = function(a) {
            a.preventDefault(), a.stopPropagation()
        }, this.saveUndoStateDeferred = function(a) {
            if (a.type == "mousemove" && a.which != 1) return;
            setTimeout(function() {
                this.detectUpdatedText(), this.saveUndoState()
            }.bind(this), 0)
        }, this.clearUndoState = function() {
            this.undoHistory = [], this.undoIndex = -1
        }, this.saveUndoState = function() {
            var a = this.htmlRich.getText(),
                b = this.htmlRich.getSelectionOffsets() || [a.length],
                c = this.undoHistory,
                d = c[this.undoIndex];
            !d || d[0] !== a ? c.splice(++this.undoIndex, c.length, [a, b]) : d && (d[1] = b)
        }, this.isUndoKey = function(a) {
            returnthis.isMac ? a.which == 90 && a.metaKey && !a.shiftKey && !a.ctrlKey && !a.altKey : a.which == 90 && a.ctrlKey && !a.shiftKey && !a.altKey
        }, this.emulateUndo = function(a) {
            this.isUndoKey(a) && (this.stopEvent(a), this.saveUndoState(), this.undoIndex > 0 && this.setUndoState(this.undoHistory[--this.undoIndex]))
        }, this.isRedoKey = function(a) {
            returnthis.isMac ? a.which == 90 && a.metaKey && a.shiftKey && !a.ctrlKey && !a.altKey : this.isWin ? a.which == 89 && a.ctrlKey && !a.shiftKey && !a.altKey : a.which == 90 && a.shiftKey && a.ctrlKey && !a.altKey
        }, this.emulateRedo = function(a) {
            var b = this.undoHistory,
                c = this.undoIndex;
            c < b.length - 1 && this.htmlRich.getText() !== b[c][0] && b.splice(c + 1, b.length), this.isRedoKey(a) && (this.stopEvent(a), c < b.length - 1 && this.setUndoState(b[++this.undoIndex]))
        }, this.setUndoState = function(a) {
            this.detectUpdatedText(!1, a[0]), this.htmlRich.setSelectionOffsets(a[1]), this.trigger("uiHideAutocomplete")
        }, this.undoStateAfterCursorMovement = function(a) {
            a.which >= 33 && a.which <= 40 && this.saveUndoStateDeferred(a)
        }, this.handleKeyDown = function(a) {
            this.isIE && this.selectPrevCharOnBackspace(a), this.attr.$browser.mozilla && this.emulateCommandArrow(a), this.undoStateAfterCursorMovement(a), this.emulateUndo(a), this.emulateRedo(a)
        }, this.interceptPaste = function(a) {
            if (a.originalEvent && a.originalEvent.clipboardData) {
                var b = a.originalEvent.clipboardData;
                (this.interceptImagePaste(b) || this.interceptTextPaste(b)) && a.preventDefault()
            }
        }, this.interceptImagePaste = function(a) {
            returnthis.triggerGotImageData && a.items && a.items.length === 1 && a.items[0].kind === "file" && a.items[0].type.indexOf("image/") === 0 ? (file.getFileData("pasted.png", a.items[0].getAsFile()).then(this.triggerGotImageData.bind(this)), !0) : !1
        }, this.interceptTextPaste = function(a) {
            var b = a.getData("text");
            return b && document.execCommand("insertHTML", !1, $("<div>").text(b).html().replace(LINE_FEEDS_REGEX, "<br>")) ? !0 : !1
        }, this.clearSelectionOnBlur = function() {
            window.getSelection && (this.previousSelection = this.htmlRich.getSelectionOffsets(), this.previousSelection && getSelection().removeAllRanges())
        }, this.restoreSelectionOnFocus = function() {
            this.previousSelection ? setTimeout(function() {
                this.htmlRich.setSelectionOffsets(this.previousSelection), this.previousSelection = null
            }.bind(this), 0) : this.previousSelection = null
        }, this.setFocusingState = function() {
            this.isFocusing = !0, setTimeout(function() {
                this.isFocusing = !1
            }.bind(this), 0)
        }, this.around("initTextNode", function(a) {
            this.isIE = this.attr.$browser.msie || navigator.userAgent.indexOf("Trident") > -1, this.$text = this.select("richSelector"), this.undoHistory = [
                ["", [0]]
            ], this.undoIndex = 0, this.htmlRich = htmlText(this.$text[0], this.attr.$browser), this.$text.toggleClass("notie", !this.isIE), this.$normalizer = this.select("normalizerSelector"), this.htmlNormalizer = htmlText(this.$normalizer[0], this.attr.$browser);
            var b = navigator.platform;
            this.isMac = b.indexOf("Mac") != -1, this.isWin = b.indexOf("Win") != -1, this.on(this.$text, "click", {
                linksSelector: this.stopEvent
            }), this.on(this.$text, "focusin", this.setFocusingState), this.on(this.$text, "keydown", this.handleKeyDown), this.on(this.$text, "focusout", this.ignoreDuringFakeFocus(this.clearSelectionOnBlur)), this.on(this.$text, "focusin", this.ignoreDuringFakeFocus(this.restoreSelectionOnFocus)), this.on(this.$text, "focusin", this.ignoreDuringFakeFocus(this.saveUndoStateDeferred)), this.on(this.$text, "cut paste drop", this.saveUndoState), this.on(this.$text, "cut paste drop mousedown mousemove", this.saveUndoStateDeferred), this.on("uiSaveUndoState", this.saveUndoState), this.on("uiClearUndoState", this.clearUndoState), this.on(this.$text, "paste", this.interceptPaste), this.detectUpdatedText()
        })
    }
    var file = require("app/utils/file"),
        htmlText = require("app/utils/html_text"),
        tweetHelper = require("app/utils/tweet_helper"),
        twitterText = require("lib/twitter-text");
    module.exports = withRichEditor;
    var INVALID_CHARS_REGEX = /[\uFFFE\uFEFF\uFFFF\u200E\u200F\u202A-\u202E\x00-\x09\x0B\x0C\x0E-\x1F]/g,
        RENDER_URLS_AS_PRETTY_LINKS = $.browser.mozilla && parseInt($.browser.version, 10) < 2,
        TRAILING_SINGLE_SPACE_REGEX = / $/,
        LINE_FEEDS_REGEX = /\r\n|\n\r|\n/g,
        MAX_OFFSET = Number.MAX_VALUE
});













define("app/ui/compose/tweet_box_manager", ["module", "require", "exports", "app/ui/compose/tweet_box", "app/ui/compose/dm_composer", "app/ui/geo_picker", "core/component", "app/ui/compose/with_rich_editor"], function(module, require, exports) {
    function tweetBoxManager() {
        this.createTweetBoxAtTarget = function(a, b) {
            this.createTweetBox(a.target, b)
        }, this.createTweetBox = function(a, b) {
            var c = $(a);
            if (!((b.eventData || {}).scribeContext || {}).component) throw new Error("Please specify scribing component for tweet box.");
            c.find(".geo-picker").length > 0 && GeoPicker.attachTo(c.find(".geo-picker"), b, {
                parent: c
            });
            var d = c.find("div.rich-editor").length > 0 ? [withRichEditor] : [],
                e = (b.dmOnly ? DmComposer : TweetBox).mixin.apply(this, d),
                f = {
                    typeaheadData: this.attr.typeaheadData
                };
            e.attachTo(c, f, b)
        }, this.after("initialize", function() {
            this.on("uiInitTweetbox", this.createTweetBoxAtTarget)
        })
    }
    var TweetBox = require("app/ui/compose/tweet_box"),
        DmComposer = require("app/ui/compose/dm_composer"),
        GeoPicker = require("app/ui/geo_picker"),
        defineComponent = require("core/component"),
        withRichEditor = require("app/ui/compose/with_rich_editor"),
        TweetBoxManager = defineComponent(tweetBoxManager);
    module.exports = TweetBoxManager
});

Obviously, this "answer" doesn't solve anything, but hopefully could provide enough to (re-)spark a conversation about this topic.

Post a Comment for "How Does Twitter Implement Its Tweet Box?"