How does Twitter implement its Clean Box?

I am trying to implement something like Twitter tweet box, in particular:

  • Automatically highlights text on a red background if the total length exceeds 140 characters.
  • Automatically highlights links, mentions, and hashtags in blue.

This should happen automatically when the user types.

By the semantic markup that I see on Twitter, it looks like they are using div contentEditable . And the inside of the DOM changes whenever a / hashtag / link is found or when the length exceeds 140 characters:

 <div aria-labelledby="tweet-box-mini-home-profile-label" id="tweet-box-mini-home-profile" class="tweet-box rich-editor notie" contenteditable="true" spellcheck="true" role="textbox" aria-multiline="true" dir="ltr" aria-autocomplete="list" aria-expanded="false" aria-owns="typeahead-dropdown-6"> <div>hello world <a class="twitter-atreply pretty-link" href="/testMention" role="presentation"><s>@</s>testMention</a> text <a href="/search?q=%23helloWorld" class="twitter-hashtag pretty-link" role="presentation"><s>#</s>helloWorld</a> text <a href="http://www.google.com" class="twitter-timeline-link" role="presentation">http://www.google.com</a> text text text text text text text text text text text text text text <em>overflow red text here</em> </div> </div> 

What have i done so far

I am currently using the contentEditable field. It calls the onChange/onInput , which parses the text. Check if it has username / link / hashtag via regexr and replace them with appropriate tags (I just use a simple <i> tag to enclose the username and hashtag for now). The problem I am facing is that since I am changing / modifying the DOM for contentEditable, the caret cursor position is lost. I looked at this topic Saving changes to range objects after selecting in HTML , and it only works if the DOM is not changed (which in my case surrounds tags / hashtags with <i> tags, links with <a> tags, and tag overflows <b> .

Fiddle: http://jsfiddle.net/4Lsqjkjb/

Does anyone have alternative solutions / approaches to solve this problem? Maybe I should not use contentEditable ? Or in some way that does not directly change the DOM of the contentEditable field to maintain the caret position? Any help would be appreciated! I tried to play with window.getSelection() and save it before changing the DOM, but it always looks reset after making changes to the DOM.

+8
javascript html range twitter selection
source share
3 answers

This is not a direct source code response that creates part of your application in your specification.

This is really not an easy task.

You're right - the way to solve this problem is to use the container contenteditable="true" . But I'm afraid that from there it will greatly facilitate .

Introduce DraftJS, a javascript full-text editing solution that does most of the hard work for you.

Both of my solutions below require using both React and DraftJS


Plug & -Play Solution:

Solution React + DraftJS + Someone else "Plugin" already exists. You can check draft-js-plugins.com . And here is the Github source code .

Personally, I would not go that way. I would rather study their GitHub source code and implement my own DraftJS entities project. Because I think React-JS-Plugins feels a bit awkward and overweight just for Links and Mentions. Plus, where do you "mention" from? Your own application? Twitter? Performing this method allows you to control where you capture the so-called โ€œmentionsโ€.


DIY Solution:

The best way I've found is to create my own set of working entities on top of a rich text editor based on DraftJS.

This of course also requires you to use React .

Forgive me for not actually creating the full working code. I was interested in doing something similar for the application that I am building in Meteor with React on the interface. So it really made sense to me, because I would add only one library (DraftJS), and the rest would be my usual entities encoded.

+3
source share

What I did is a bit of a hack, but it works very well as a workaround.

I have a simple text area (uncompetitive due to what's next, I will explain below) and a div that is completely located behind the text field. Using javascript, I copy the content from the text box to the div, splitting it into 140 characters and putting all the extra characters in the <em /> .

Well, this is a little more complicated because the twitter algorithm for calculating the length of the tweet is different (links are not considered their real value due to the shortening of the t.co URL). The exact method can be found in the official twitter / twitter-txt repository .

Step by step code.

Wrap a simple text area in a div to simplify 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 only makes textarea and div right above each other and highlights 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-back em { background: #fcc !important; } 

Now the fun part is, this is implemented using jQuery, but that is not the point.

 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 an event handler when changing and the overall document is ready, and you're done. See the codepen link below.

Note that the hosted div can be created using js 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 a working example here: http://codepen.io/hussard/pen/EZvaBZ

+2
source share

It turns out that this is really not an easy task. I struggled with this over the past few days, and I'm not very close to a solution.

The best solution for the dropdown menu is the At.js library , which is still supported, but definitely not perfect. One example shows how you can make text selection.

The most unpleasant part of this problem is that Twitter has a great solution that seems to work just fine with us right in the face. I spent some time learning how they implement their tweet box, and this is definitely not trivial. It seems like they do almost everything manually, including emulating undo / redo functions, intercepting copies / pastes, providing custom code for IE / W3C, custom Mac / PC encoding and more. They use a content-accessible div, which in itself is problematic due to differences in browser implementations. This is really impressive.

Here is the most relevant (unfortunately confusing) code taken from the Twitter JavaScript download file for Twitter (found by checking the title of the main page on Twitter). I did not want to directly copy and paste the link if it was personalized in 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) } else if (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)) } else if (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) return null; var d = b.getRangeAt(0); if (isOutsideContainer(d.commonAncestorContainer, a)) return null; 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)) return null; 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) }; return this.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) { return this.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) { return this.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) { return this.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โ€ does not solve anything, but I hope that one could provide enough to (re) trigger a conversation about this topic.

+1
source share

All Articles