Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
52 lines
No EOL
14 KiB
JavaScript
52 lines
No EOL
14 KiB
JavaScript
;'use strict';var Compose=(function(){const TYPES=['image','audio','video'];const UPDATE_DELAY=500;const MIN_AVAILABLE_CHARS_COUNT=20;var placeholderClass='placeholder';var attachmentClass='attachment-container';var attachments=new Map();var dom={form:null,message:null,sendButton:null,attachButton:null,counter:null};var state={empty:true,maxLength:null,size:null,lastScrollPosition:0,resizing:false,type:'sms',lock:{canSend:null,canEdit:null,forceType:null},segmentInfo:{segments:0,charsAvailableInLastSegment:0}};var subject=null;function getContentFromDOM(domElement){var content=[];var node;for(node=domElement.firstChild;node;node=node.nextSibling){var attachment=attachments.get(node);if(attachment){content.push(attachment);continue;}
|
|
var last=content.length-1;var text=node.textContent;if(text){text=text.replace(/\u00A0/g,' ');}
|
|
if(node.nodeName=='BR'){if(node===domElement.lastChild){continue;}
|
|
text='\n';}
|
|
if(text.length){if(typeof content[last]==='string'){content[last]+=text;}else{content.push(text);}}}
|
|
return content;}
|
|
function onContentChanged(duck){if((duck instanceof Attachment&&duck.type==='img')||(duck&&duck.containsImage)){return imageAttachmentsHandling();}
|
|
var isEmptyMessage=!dom.message.textContent.length&&!hasAttachment();if(isEmptyMessage){var brs=dom.message.getElementsByTagName('br');if(brs.length>1){isEmptyMessage=false;}}
|
|
var placeholding=dom.message.classList.contains(placeholderClass);if(placeholding&&!isEmptyMessage){dom.message.classList.remove(placeholderClass);}
|
|
if(!placeholding&&isEmptyMessage){dom.message.classList.add(placeholderClass);}
|
|
state.emptyMessage=isEmptyMessage;Compose.updateEmptyState();Compose.updateType();Compose.updateSendButton();Compose.updateAttachButton();updateSegmentInfoThrottled();Compose.emit('input');}
|
|
function onSubjectChanged(){Compose.updateEmptyState();Compose.updateType();Compose.updateSendButton();Compose.updateAttachButton();Compose.emit('subject-change');}
|
|
function onSubjectVisibilityChanged(){if(subject.isVisible()){dom.form.classList.add('subject-composer-visible');}else{dom.message.focus();dom.form.classList.remove('subject-composer-visible');}}
|
|
function hasAttachment(){return!!dom.message.querySelector('iframe');}
|
|
function hasSubject(){return subject.isVisible()&&!!subject.getValue();}
|
|
function composeKeyEvents(e){if(!compose.canEdit()&&e.which!==8){e.preventDefault();}else{state.size=null;}}
|
|
function insert(item){var fragment=document.createDocumentFragment();if(!item){return null;}
|
|
state.size=null;if(item.render){var node=item.render();attachments.set(node,item);fragment.appendChild(node);}else if(item.nodeName==='IFRAME'){fragment.appendChild(item);}else if(typeof item==='string'){item.split('\n').forEach(function(line){fragment.appendChild(document.createTextNode(line));fragment.appendChild(document.createElement('br'));});fragment.lastElementChild.remove();}
|
|
return fragment;}
|
|
function imageAttachmentsHandling(){if(Compose.size<Settings.mmsSizeLimitation){onContentChanged();return;}
|
|
var imgNodes=[];attachments.forEach((attachment,node)=>{if(attachment.type==='img'){imgNodes.push(node);}});var done=0;var images=imgNodes.length;var limit=images>2?Settings.mmsSizeLimitation*0.2:Settings.mmsSizeLimitation*0.4;function imageSized(){if(++done===images){state.resizing=false;onContentChanged();}else{resizedImg(imgNodes[done]);}}
|
|
function resizedImg(node){var item=attachments.get(node);if(item.blob.size<limit){imageSized();}else{Utils.getResizedImgBlob(item.blob,limit,function(resizedBlob){state.size=null;item.blob=resizedBlob;item.updateFileSize();imageSized();});}}
|
|
state.resizing=true;resizedImg(imgNodes[0]);onContentChanged();}
|
|
var segmentInfoTimeout=null;function updateSegmentInfoThrottled(){if(hasAttachment()){return resetSegmentInfo();}
|
|
if(segmentInfoTimeout===null){segmentInfoTimeout=setTimeout(updateSegmentInfo,UPDATE_DELAY);}}
|
|
function updateSegmentInfo(){segmentInfoTimeout=null;var value=Compose.getText();var segmentInfoPromise=value?MessageManager.getSegmentInfo(value):Promise.reject();segmentInfoPromise.then(function(segmentInfo){state.segmentInfo=segmentInfo;},resetSegmentInfo).then(compose.updateType.bind(Compose)).then(compose.emit.bind(compose,'segmentinfochange'));}
|
|
function resetSegmentInfo(){state.segmentInfo={segments:0,charsAvailableInLastSegment:0};}
|
|
function disposeAttachmentNode(attachmentNode){var thumbnailURL=attachmentNode.dataset.thumbnail;if(thumbnailURL){window.URL.revokeObjectURL(thumbnailURL);}
|
|
attachments.delete(attachmentNode);}
|
|
var compose={init:function composeInit(formId){dom.form=document.getElementById(formId);dom.message=document.getElementById('messages-input');dom.sendButton=document.getElementById('messages-send-button');dom.attachButton=document.getElementById('messages-attach-button');dom.counter=dom.form.querySelector('.js-message-counter');dom.messagesAttach=dom.form.querySelector('.messages-attach-container');dom.composerButton=dom.form.querySelector('.composer-button-container');subject=new SubjectComposer(dom.form.querySelector('.js-subject-composer'));subject.on('change',onSubjectChanged);subject.on('visibility-change',onSubjectChanged);subject.on('visibility-change',onSubjectVisibilityChanged);dom.message.addEventListener('input',onContentChanged);dom.message.addEventListener('keydown',composeKeyEvents);dom.message.addEventListener('keypress',composeKeyEvents);dom.message.addEventListener('click',this.onAttachmentClick.bind(this));dom.attachButton.addEventListener('click',this.onAttachClick.bind(this));this.offAll();this.clear();this.on('type',this.onTypeChange.bind(this));this.on('type',this.updateMessageCounter.bind(this));this.on('segmentinfochange',this.updateMessageCounter.bind(this));var onInteracted=this.emit.bind(this,'interact');dom.message.addEventListener('focus',onInteracted);dom.message.addEventListener('click',onInteracted);dom.messagesAttach.addEventListener('click',onInteracted);dom.composerButton.addEventListener('click',onInteracted);subject.on('focus',onInteracted);return this;},getContent:function(){return getContentFromDOM(dom.message);},getSubject:function(){return subject.isVisible()?subject.getValue():null;},setSubject:function(value){return subject.setValue(value);},isSubjectMaxLength:function(){return subject.getValue().length>=subject.getMaxLength();},showSubject:function(){subject.show();subject.focus();},hideSubject:function(){subject.hide();},fromDraft:function(draft){this.clear();if(!draft){return;}
|
|
if(draft.subject){this.setSubject(draft.subject);subject.show();}
|
|
if(!draft.content){return;}
|
|
if(!Array.isArray(draft.content)){throw new Error('If present, the `content` property must be an array.');}
|
|
draft.content.forEach(function(fragment){if(fragment.blob){fragment=new Attachment(fragment.blob,{name:fragment.name,isDraft:true});}
|
|
Compose.append(fragment);},Compose);},getText:function(){var out=this.getContent().filter(function(elem){return(typeof elem==='string');});return out.join('');},isEmpty:function(){return state.empty;},setupLock({canSend,canEdit,forceType}){state.lock.canSend=canSend;state.lock.canEdit=canEdit;state.lock.forceType=forceType;},refresh(){this.updateType();this.updateSendButton();this.updateAttachButton();},scrollToTarget:function(target){var targetRect=target.getBoundingClientRect();var containerRect=dom.message.getBoundingClientRect();var offset=(targetRect.top+targetRect.height/2)-
|
|
(containerRect.top+containerRect.height/2);dom.message.scrollTop+=offset;},scrollMessageContent:function(){if(document.activeElement===dom.message){var range=window.getSelection().getRangeAt(0).cloneRange();if(range.collapsed){range.insertNode(document.createElement('span'));this.scrollToTarget(range);range.deleteContents();}else{this.scrollToTarget(range);}
|
|
state.lastScrollPosition=dom.message.scrollTop;}else{dom.message.scrollTop=state.lastScrollPosition;}},prepend:function(item){var fragment=insert(item);if(!fragment){return this;}
|
|
if(dom.message.firstChild&&dom.message.firstChild.nodeName==='BR'){dom.message.insertBefore(fragment,dom.message.childNodes[1]);}else{dom.message.insertBefore(fragment,dom.message.childNodes[0]);}
|
|
onContentChanged(item);return this;},append:function(item,options){options=options||{};if(Array.isArray(item)){var containsImage=false;item.forEach(function(content){if(content.type==='img'){containsImage=true;}
|
|
this.append(content,{ignoreChange:true});},this);onContentChanged({containsImage:containsImage});}else{var fragment=insert(item);if(!fragment){return this;}
|
|
if(document.activeElement===dom.message){var range=window.getSelection().getRangeAt(0);var firstNodes=fragment.firstChild;range.deleteContents();range.insertNode(fragment);this.scrollToTarget(range);dom.message.focus();range.setStartAfter(firstNodes);}else{dom.message.insertBefore(fragment,dom.message.lastChild);this.scrollToTarget(dom.message.lastChild);}
|
|
if(!options.ignoreChange){onContentChanged(item);}}
|
|
return this;},clear:function(){state.type='sms';this.onTypeChange();attachments.forEach((attachment,node)=>disposeAttachmentNode(node));dom.message.innerHTML='<br>';subject.reset();state.resizing=false;state.size=0;resetSegmentInfo();segmentInfoTimeout=null;onContentChanged();return this;},focus:function(){dom.message.focus();var selection=window.getSelection();var range=document.createRange();var lastChild=dom.message.lastChild;if(lastChild.tagName==='BR'){range.setStartBefore(lastChild);}else{range.setStartAfter(lastChild);}
|
|
selection.removeAllRanges();selection.addRange(range);return this;},updateType:function(){var newType=this.getType();if(newType!==state.type){state.type=newType;this.emit('type');}},getType(){var typeForcedByLock=state.lock.forceType&&state.lock.forceType();if(typeForcedByLock){return typeForcedByLock;}
|
|
var isTextTooLong=state.segmentInfo.segments>Settings.maxConcatenatedMessages;return isTextTooLong||hasSubject()||hasAttachment()?'mms':'sms';},updateEmptyState:function(){state.empty=state.emptyMessage&&!hasSubject();},updateSendButton:function(){dom.sendButton.disabled=!this.canSend();},updateAttachButton:function(){dom.attachButton.disabled=!this.canEdit();},canSend(){var isSendAllowedByLock=!state.lock.canSend||state.lock.canSend();var sizeExceedsLimit=this.type==='mms'&&Settings.mmsSizeLimitation&&this.size>Settings.mmsSizeLimitation;return isSendAllowedByLock&&!sizeExceedsLimit&&!this.isEmpty()&&!this.isResizing;},canEdit(){var isEditAllowedByLock=!state.lock.canEdit||state.lock.canEdit();var sizeExceedsOrEqualToLimit=this.type==='mms'&&Settings.mmsSizeLimitation&&this.size>=Settings.mmsSizeLimitation;return isEditAllowedByLock&&!sizeExceedsOrEqualToLimit;},_onAttachmentRequestError:function c_onAttachmentRequestError(err){var errId=err instanceof DOMError?err.name:err.message;if(errId==='file too large'){Utils.alert({id:'attached-files-too-large',args:{n:1,mmsSize:(Settings.mmsSizeLimitation/1024).toFixed(0)}});}else if(errId!=='ActivityCanceled'&&errId!=='pick cancelled'){console.warn('Unhandled error: ',err);}},onAttachClick:function c_onAttachClick(event){this.requestAttachment().then(this.append.bind(this),this._onAttachmentRequestError);},onAttachmentClick:function c_onAttachmentClick(event){if(!event.target.classList.contains(attachmentClass)||state.resizing){return;}
|
|
var currentAttachmentDOM=event.target;var currentAttachment=attachments.get(event.target);var name=currentAttachment.name;var blob=currentAttachment.blob;var options={header:name.substr(name.lastIndexOf('/')+1)};var mimeFirstPart=blob.type.substr(0,blob.type.indexOf('/'));var fileType=(TYPES.indexOf(mimeFirstPart)>-1)?mimeFirstPart:'other';var onView=()=>{currentAttachment.view();};function onRemove(){dom.message.removeChild(currentAttachmentDOM);disposeAttachmentNode(currentAttachmentDOM);state.size=null;onContentChanged();}
|
|
var onReplace=()=>{this.requestAttachment().then((newAttachment)=>{var fragment=insert(newAttachment);dom.message.insertBefore(fragment,currentAttachmentDOM);dom.message.removeChild(currentAttachmentDOM);disposeAttachmentNode(currentAttachmentDOM);onContentChanged(newAttachment);},this._onAttachmentRequestError);};options.items=[{l10nId:`view-attachment-${fileType}`,method:onView},{l10nId:`remove-attachment-${fileType}`,method:onRemove},{l10nId:`replace-attachment-${fileType}`,method:onReplace},{l10nId:'cancel'}];new OptionMenu(options).show();},onTypeChange:function c_onTypeChange(){if(this.type==='sms'){dom.message.setAttribute('x-inputmode','-moz-sms');}else{dom.message.removeAttribute('x-inputmode');}
|
|
dom.form.dataset.messageType=this.type;},updateMessageCounter:function c_updateMessageCounter(){var counterValue='';if(this.type==='sms'){var segments=state.segmentInfo.segments;var availableChars=state.segmentInfo.charsAvailableInLastSegment;if(segments&&(segments>1||availableChars<=MIN_AVAILABLE_CHARS_COUNT)){counterValue=availableChars+'/'+segments;}}
|
|
if(counterValue!==dom.counter.textContent){dom.counter.textContent=counterValue;}},requestAttachment:function(){var activityData={type:['image/*','audio/*','video/*','text/vcard']};var activity;var defer=Utils.Promise.defer();if(Settings.mmsSizeLimitation){activityData.maxFileSizeBytes=Settings.mmsSizeLimitation;}
|
|
activity=new MozActivity({name:'pick',data:activityData});activity.onsuccess=function(){var result=activity.result;if(Settings.mmsSizeLimitation&&result.blob.size>Settings.mmsSizeLimitation&&Utils.typeFromMimeType(result.blob.type)!=='img'){defer.reject(new Error('file too large'));}
|
|
defer.resolve(new Attachment(result.blob,{name:result.name,isDraft:true}));};activity.onerror=()=>{defer.reject(activity.error);};return defer.promise;}};Object.defineProperty(compose,'type',{get:function composeGetType(){return state.type;}});Object.defineProperty(compose,'size',{get:function composeGetSize(){if(state.size===null){state.size=this.getContent().reduce(function(sum,content){if(typeof content==='string'){return sum+content.length;}else{return sum+content.size;}},0);}
|
|
return state.size;}});Object.defineProperty(compose,'segmentInfo',{get:function composeGetSegmentInfo(){return state.segmentInfo;}});Object.defineProperty(compose,'isResizing',{get:function composeGetResizeState(){return state.resizing;}});Object.defineProperty(compose,'isSubjectVisible',{get:function composeIsSubjectVisible(){return subject.isVisible();}});return EventDispatcher.mixin(compose,['input','type','segmentinfochange','interact','subject-change']);}()); |