推特是如何实现其推特盒子的
How does Twitter implement its Tweet Box?
我正在尝试实现类似Twitter的推特框的东西,特别是:
- 当总长度超过140个字符时,自动高亮显示红色背景中的文本
- 自动以蓝色突出显示链接、提及和标签
这些应该自动发生,也就是当用户键入时。
从我在Twitter上看到的语义标记来看,他们似乎使用了contentEditable
div。每当检测到提及/标签/链接,或者长度超过140个字符时,就会修改里面的DOM:
<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>
到目前为止我做了什么
目前,我使用的是contentEditable字段。它触发了一个解析文本的函数onChange/onInput
。通过regexr检查它是否有用户名/链接/标签,并用相应的标签替换它们(我现在只是使用一个简单的<i>
标签来括起用户名和标签)。我遇到的问题是,因为我正在更改/修改contentEditable的DOM,插入符号光标的位置会丢失我看了这个线程,它在HTML中持久化选择后范围对象的变化,只有在DOM没有改变的情况下(在我的情况下,用<i>
标签包围标签/主题标签、用<a>
标签链接和用<b>
标签溢出),它才能正常工作
Fiddle:http://jsfiddle.net/4Lsqjkjb/
关于如何解决这个问题,有人有其他解决方案/方法吗?也许我不应该使用contentEditable
?或者不直接修改contentEditable
字段的DOM以保持插入符号位置的某种方式?任何帮助都将不胜感激!我试着玩window.getSelection()
,并在DOM更改之前保存它,但它似乎总是在应用DOM更改后重置。
我所做的是一种破解,但作为一种变通解决方案效果很好。
我有一个简单的文本区域(不可编辑内容,因为接下来会发生什么,我将在下面解释)和一个绝对位于文本区域后面的div。使用javascript,我将内容从文本区域复制到div,同时将其拆分为140个字符,并将所有多余的字符放入<em />
标记中。
嗯,这有点复杂,因为计算推特长度的推特算法不同(由于t.co url缩短,链接不计入其真实价值)。确切的方法可以在官方twitter/twitter txt存储库中找到。
逐步代码
将一个简单的文本区域包装成div以简化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只是将textarea和div放在彼此的正上方,并突出显示文本。
.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;
}
现在有趣的部分是,这是使用jQuery实现的,但这并不重要。
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('');
}
在更改时添加一些事件处理程序,并准备好公共文档,就完成了。请参阅下面的代码笔链接。
注意,在加载页面时,放置在后面的div也可以使用js创建:
// Create a pseudo-element that will be hidden behind the placeholder.
var $placeholderBacker = $('<div class="js-keeper-placeholder-back"></div>');
$placeholderBacker.insertAfter($textarea);
完整示例
请参阅此处的工作示例:http://codepen.io/hussard/pen/EZvaBZ
这不是源代码根据您的规范构建应用程序部分的直接答案。
这真的不是一件容易的事。
你是对的,解决这个问题的方法是使用contenteditable="true"
容器。但我担心,从那时起,事情会变得更加复杂。
输入DraftJS-富文本编辑的javascript解决方案,它为您完成了大部分繁重的工作。
我下面的两个解决方案都需要使用React和DraftJS
插头-播放解决方案:
React+DraftJS+其他人的"插件"解决方案已经在这里了。你可以查看draft-js-plugins.com。这是Github的源代码。
就我个人而言,我不会走这条路。我宁愿研究他们的GitHub源代码并实现我自己的DraftJS entities
。因为我认为React JS插件对于链接和提及来说感觉有点笨重和超重。另外,你是从哪里"提到"的?您自己的应用程序?啁啾这样做可以让你控制自己在哪里获得所谓的"提及"。
DIY解决方案:
我发现的最好的方法是在基于DraftJS的富文本编辑器之上构建一套自己的工作entities
。
当然,这也需要您使用React。
请原谅我没有真正构建一套完整的工作代码。我有兴趣为我正在Meteor中构建的前端有React的应用程序做一些类似的事情。所以这对我来说真的很有意义,因为我只会再添加一个库(DraftJS),其余的都是我的自定义entities
。
事实证明,这真的不是一件容易的事。过去几天我一直在努力解决这个问题,我还没有找到解决方案。
您目前最好的插件解决方案是At.js库,它仍在维护中,但肯定不是完美的。其中一个示例显示了如何类型的进行文本高亮显示。
这个问题最令人讨厌的部分是,推特有一个漂亮的解决方案,它似乎完美地盯着我们。我花了一些时间调查他们实现"推特框"的方式,这绝对不是小事。看起来他们几乎所有的工作都是手动完成的,包括模拟撤消/重做功能、拦截复制/粘贴、为IE/WW3C提供自定义代码、对Mac/PC进行自定义编码等等。它们使用了一个内容可编辑的div,由于浏览器实现的差异,这个div本身就有问题。事实上,这非常令人印象深刻。
这是最相关的(不幸的是,混淆了)代码,取自Twitter的启动JavaScript文件(通过检查登录的Twitter主页的标题找到)。我不想直接复制和粘贴链接,以防它被个性化到我的推特账户。
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 "), b.mozilla ? c + "<BR>" : "<P>" + c + "</P>") : (c = (c || "<br>").replace(SP_LEADING_OR_TRAILING_OR_FOLLOWING_A_SP_REGEX, "$1 "), 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
});
显然,这个"答案"并不能解决任何问题,但希望能提供足够的答案来(重新)引发关于这个话题的对话。
- 如何使用动画实现纸张推车
- 客户端服务器REST API captcha实现
- 如何实现此布局
- Meteor忘记了密码的实现
- 使用Native Sockets在Android中实现WebSockets
- 在样板文件中实现Ajax
- instanceof是如何在JavaScript中实现的
- 如何正确实现Jquery多选小部件
- 实现一个建立在google.com之上的自定义搜索引擎
- 多个组件是如何实现的
- window.location使用jquery mobile实现chrome跳转
- 如何在OpenERP中实现网络摄像头
- Node.js使用Series函数(模式?)实现流控制时出现意外结果
- javascript加密实现,包括可信否认
- 实现比较方法的最佳实践是什么;s的比较类型是在运行时选择的
- 如何让程序员在javascript中实现正确的回调
- 如何使用自定义辅助对象(handler)实现嵌套的每个循环
- AngularJS智能表全局配置实现
- Expressjs/AngularJS:实现req-flash后出错
- 推特是如何实现其推特盒子的