Tweetbox
tweetbox
by Mark Benedict Santiago
HTML
<div class="tw-inline-login clearfix">
<div class="profile-image">
<div class="image" style="background-image: url('{{twitterUser('profile_image_url')}}')"></div>
</div>
<div class="tweetbox">
<div class="input-area">
<twinput max=140 contenteditable="true" id="editor" placeholder="What's happening?" ng-model="tweetText" ng-class="{'has-photo': tweetImage}"></twinput>
<div class="photo-container" ng-class="{'has-photo': tweetImage}">
<div class="photo" style="background-image:url('{{thumbnailUrl}}')">
<span class="glyphicon glyphicon-remove remove" ng-click="removeImage()"></span>
</div>
</div>
</div>
<div class="buttons">
<span class="pull-right">
<span class="counter" ng-bind="tweetLength(tweetText, offset)" class="pull-right text-muted"></span>
<button class="btn btn-primary tweet" type="button" ng-disabled="(!tweetText || tweetText.length < 1) && (!tweetImage || !fixed) || (tweetLength(tweetText, offset) < 0)" ng-click="tweet(fixed)">
<i class="fa fa-twitter"></i> Tweet
</button>
</span>
<div class="btn btn-default btn-file tw-image pull-left" ng-hide="tweetImage">
<span class="glyphicon glyphicon-camera"></span> Add Photo
<input type="file" name="picture" on-selected-change="onFileSelect(file)">
</div>
</div>
<span ng-bind-html="tw_message"></span>
</div>
</div>
CSS
.tw-inline-login, .tw-inline-form{
margin: 20px auto;
width: 100%;
min-width: 450px;
max-width: 600px;
.profile-image{
float: left;
width: 10%;
.image{
border-radius: 6px;
width: 35px;
height: 35px;
background-size: contain;
}
}
.tweetbox{
float: left;
width: 90%;
.input-area{
border-radius: 4px;
margin-bottom: 10px;
twinput{
// appearance
background-color: white;
border: thin solid lightblue;
border-radius: inherit;
cursor: text;
// box
display: block;
vertical-align: top;
padding: 8px 10px;
// height
overflow-y: auto;
min-height: 35px;
max-height: 240px;
// width
min-width: 100px;
width: 100%;
// others
word-wrap: break-word;
// font
font-size: 14px;
line-height: 20px;
// make it look like a textarea
-webkit-user-modify: read-write;
-webkit-line-break: after-white-space;
direction: ltr;
unicode-bidi: embed;
span.red{
background-color: lightpink;
}
}
twinput.expand{
min-height: 80px;
}
twinput:focus{
outline: none;
}
twinput.has-photo{
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
border-bottom: none;
}
twinput:empty:not(:focus):before{
color: dodgerblue;
content:attr(placeholder)
}
.photo-container{
display: none;
border: thin solid lightblue;
border-bottom-left-radius: inherit;
border-bottom-right-radius: inherit;
height: 50px;
width: 100%;
padding: 5px;
.photo{
display: inline-block;
float: left;
border-radius: 2px;
width: 40px;
height: 40px;
background-size: cover;
background-repeat: no-repeat;
position: relative;
.remove{
color: white;
background-color: rgb(56, 56, 56);
font-size: 10pt;
opacity: 0.5;
float: right;
border-bottom-left-radius: 3px;
padding:...
JavaScript
.directive('twinput', function(Twitter, $sce) {
return {
restrict: 'E',
require: '?ngModel',
link: function(scope, element, attr, model){
if(!model) return
// initialize
rangy.init();
var classApplier = rangy.createClassApplier("red");
var editor = element[0];
var handleEditorChangeEvent = (function() {
var timer;
function debouncer() {
if (timer) timer = null;
highlightExcessCharacters();
}
return function() {
if (timer) window.clearTimeout(timer);
timer = window.setTimeout(debouncer, 10);
};
})();
function highlightExcessCharacters() {
// Save selection
var sel = rangy.getSelection();
var savedSel = sel.saveCharacterRanges(editor);
// Remove previous highlight
var range = rangy.createRange();
range.selectNodeContents(editor);
var editorCharCount = range.text().length;
classApplier.undoToRange(range);
// Create a range selecting the excess characters
range.selectCharacters(editor, attr.max, editorCharCount);
// Highlight the excess
classApplier.applyToRange(range);
// Restore selection
sel.restoreCharacterRanges(editor, savedSel);
}
function setCaretToEnd(){
//Set caret to end
var selection = rangy.getSelection();
selection.selectAllChildren(element[0]);
selection.collapseToEnd();
}
element.on('keyup keydown cut copy paste', function() {
// assign element value to model
scope.$evalAsync(read);
// highlight excess
handleEditorChangeEvent();
});
model.$render = function() {
element.html($sce.getTrustedHtml(model.$viewValue));
handleEditorChangeEvent();
if(element.text().trim() == scope.hashes) setCaretToEnd();
};
function read() {
var html = element.text();
model.$setViewValue(html);
}
read(); // initialize
element.on('focus', function(){
if(scope.tweetText == "" || scope.tweetText == undefined){
scope.hashes =...