diff --git a/css/base.css b/css/base.css
index 119f2331..d0b37da8 100644
--- a/css/base.css
+++ b/css/base.css
@@ -1279,10 +1279,12 @@ i.search {
#image-preview-caption-panel {
display: flex;
justify-content: space-between;
- background-color: transparent;
+ background-color: #eceff1;
+ box-shadow: 0 1px 2px silver; /* Faint shadow under the panel */
line-height: 2.5rem;
- height: 3.5rem;
+ height: 4.6rem;
padding: 1rem 0.75rem 0 0.75rem;
+ z-index: 3;
}
#image-preview-caption-panel a {
@@ -1308,7 +1310,8 @@ i.search {
justify-content: space-around;
height: 3.2rem;
line-height: 1.5rem;
- background-color: transparent;
+ background-color: #eceff1;
+ box-shadow: 0px -2px 3px -1px rgba(0,0,0,0.1); /* Faint shadow above panel */
}
/* Talk bubbles */
diff --git a/css/base.min.css b/css/base.min.css
index d3f7b496..a213e020 100644
--- a/css/base.min.css
+++ b/css/base.min.css
@@ -1 +1 @@
-*{box-sizing:border-box;padding:0;margin:0;font-family:Roboto,Arial,sans-serif;text-rendering:optimizeLegibility}body,html{height:100%;width:100%;font-size:10pt;color:#666;overflow:hidden;background-color:silver;box-shadow:inset 0 10rem #3949ab}tt{font-family:Roboto Mono,Courier,monospace}a{color:#2196f3;text-decoration:none}a:focus,a:hover{color:#0a6ebd;text-decoration:underline}a.red:focus,a.red:hover{color:#f44336}a.flat-button{display:flex;align-items:center;margin:.15rem .35rem;height:2em;line-height:2em;font-weight:400;text-align:center;vertical-align:middle;touch-action:manipulation;white-space:nowrap;text-transform:uppercase}a.flat-button:focus,a.flat-button:hover{text-decoration:none}form{padding:.5rem .75rem;line-height:1.5}form,label{font-size:1.1rem}label{margin-right:auto}label.small{color:#1e88e5;font-size:.95rem}img.avatar{width:100%;height:100%;border-radius:50%}button{display:inline-block;font-family:inherit;font-size:1.05rem;height:2.2rem;line-height:2.2rem;font-weight:400;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;border:1px solid transparent;white-space:nowrap;padding-left:1.2rem;padding-right:1.2rem;border-radius:4px;box-shadow:0 2px 3px 0 rgba(0,0,0,.25);text-transform:uppercase;min-width:5rem;margin:0 .35rem}button[disabled],html input[disabled]{cursor:default;opacity:.65;box-shadow:none}button.round{border-radius:50%;width:3rem;height:3rem;padding:0;margin:0;line-height:3.5rem;outline:none;box-shadow:0 2px .5rem 0 rgba(0,0,0,.25);z-index:2}button.round.small{width:2rem;height:2rem;line-height:2rem;box-shadow:0 2px .35rem 0 rgba(0,0,0,.25)}button:active,button:focus,button:hover{color:#fff}button:active{box-shadow:inset 0 3px .5rem rgba(0,0,0,.125)}button:active.round.small{box-shadow:inset 0 2px .35rem rgba(0,0,0,.125)}button.blue:active,button.blue:focus,button.blue:hover{background-color:#1976d2}button.blue{background-color:#1e88e5;color:#fff}button.blue:active,button.blue:focus,button.blue:hover{background-color:#2962ff}button.white{background-color:#fff;color:#2196f3}button.blue:active,button.white:focus,button.white:hover{background-color:#e3f2fd}button.outline{background-color:transparent;color:#2196f3;border:1px solid silver;box-shadow:none}button.outline:active,button.outline:focus,button.outline:hover{background-color:#e8eaf6}input,textarea{font-family:inherit;font-size:1.1rem;background:transparent;border:none;margin:.25rem 0;padding:0 .125rem;outline:none;color:#666}textarea{resize:none}input[type=email],input[type=password],input[type=search],input[type=text],textarea{height:2.5rem;border-bottom:1px solid #ccc;box-shadow:0 1px 0 rgba(0,0,0,.1);transition:all .1s ease-in-out}input[type=email],input[type=password],input[type=text],textarea{width:100%}input[type=email]:focus,input[type=password]:focus,input[type=search]:focus,input[type=text]:focus,textarea:focus{border-bottom:1px solid #2196f3;box-shadow:0 1px 0 rgba(33,150,243,.25)}input[type=email].invalid,input[type=password].invalid,input[type=search].invalid,input[type=text].invalid,textarea.invalid{border-bottom:1px solid #e57373;box-shadow:0 1px 0 rgba(229,115,115,.25)}input[type=email]::placeholder,input[type=password]::placeholder,input[type=search]::placeholder,input[type=text]::placeholder,textarea::placeholder{color:#bbb;font-weight:lighter}input[type=password].with-visibility,input[type=text].with-visibility{margin-right:-2rem;padding-right:2rem}::-webkit-scrollbar{width:.6rem}::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.2);background-color:hsla(0,0%,100%,.1)}::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.3)}*{scrollbar-width:thin;scrollbar-color:silver}ul.menu{position:absolute;list-style:none;width:12rem;border-radius:.2rem;padding-top:.35rem;padding-bottom:.35rem;box-shadow:0 1px 6px rgba(0,0,0,.45);background-color:#fff;z-index:4}ul.menu>li{padding:.75rem .75rem .75rem 1.25rem}ul.menu>li:hover{background-color:#f5f5f5}ul.menu>li.separator{padding:0;margin:2px;height:1px;background-color:#ccc}ul.menu>li.separator:hover{background-color:#ccc}li .menuTrigger{position:absolute;top:.25rem;right:.25rem;width:1.5rem;height:1.2rem;line-height:1.2rem;border-radius:.6rem;text-align:center;opacity:0}li:hover .menuTrigger{opacity:1}li:hover .menuTrigger a{color:#757575}li.left:hover .menuTrigger{-text-shadow:#c5e1a5 0 0 15px,#c5e1a5 0 0 5px;background-image:radial-gradient(#c5e1a5 40%,transparent 75%)}li.right:hover .menuTrigger{-text-shadow:#fafafa 0 0 15px,#fafafa 0 0 5px;background-image:radial-gradient(#fafafa 40%,transparent 75%)}div.alert-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%;position:absolute;-x-backdrop-filter:blur(4px);background:rgba(0,0,0,.33);z-index:3}div.alert{background-color:#fff;box-shadow:0 1px 6px rgba(0,0,0,.45);padding:1rem;border-radius:4px;margin:auto 2rem;width:content;min-width:16rem;max-width:24rem}div.alert .title{color:#2196f3;font-size:1.2rem;font-weight:500;width:100%;padding:0 0 .35rem}div.alert .content{padding:.5rem 0}.blue{color:#2196f3}.red{color:#d32f2f}.amber{color:#ffc107}.gray{color:#757575}.light-gray{color:#bbb}.small{font-size:90%}.hr{display:block;width:100%;height:.45rem;min-height:.3rem;background-color:#f0f0f0;box-shadow:inset 0 .25rem .25rem -.25rem rgba(0,0,0,.15),inset 0 -.25rem .25rem -.25rem rgba(0,0,0,.15)}.group-focus{outline:none}#mountPoint{height:100%;padding-top:1rem;padding-bottom:3rem}#noscript-message{position:relative;display:flex;flex-direction:column;max-width:30rem;overflow:hidden;margin-top:5rem;margin-left:auto;margin-right:auto;border-radius:.25rem;box-shadow:1px 2px .3rem grey;background-color:#fff}#noscript-title{padding:1rem;font-size:120%;font-weight:bolder;background-color:#2196f3;color:#bbdefb}#noscript-content{padding:1rem}#app-container{position:relative;display:flex;height:100%;max-width:80rem;overflow:hidden;margin-left:auto;margin-right:auto;border-radius:.25rem;box-shadow:1px 2px .3rem grey;background-color:#fff}#sidepanel{flex-grow:0;flex-shrink:0.3;width:24rem;min-width:18rem}#sidepanel,#topic-view{position:relative;display:flex;flex-direction:column;height:100%}#topic-view{overflow-x:hidden;width:100%}#info-view{display:flex;flex-direction:column;flex-grow:0;flex-shrink:0.3;width:24rem;min-width:18rem;height:100%}@media (max-width:960px){#mountPoint{padding:0}}@media (max-width:640px){#app-container{width:100%;border-radius:0;box-shadow:none}#info-view,#sidepanel{width:100%}}.center-medium-text{width:100%;height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-size:1.4rem}.nodisplay{display:none!important}.quoted{margin-left:1rem}.in-place-edit{display:inline-block;font-family:inherit;font-size:1.1rem;height:2.5rem;line-height:2.25;margin:.25rem .125rem;width:100%;vertical-align:baseline;border-bottom:1px dashed #ddd;cursor:pointer}.in-place-edit.disabled{border-bottom:none;cursor:default}.in-place-edit.placeholder{color:#bbb}.in-place-edit .content{display:block;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}table.permission-editor{width:100%}table.permission-editor td{margin:.35rem 0;font-size:1.1rem;vertical-align:middle}table.permission-editor td.checkbox{text-align:center;color:#999}.permission-editor .material-icons{font-size:1.1rem;line-height:1.1rem}.material-icons.clickable{cursor:pointer;border-bottom:none}.clickable{border-bottom:1px dashed #ddd}.clean-clickable,.clickable,.image-clickable{cursor:pointer}.panel-title{font-size:1.4rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.caption-panel{display:flex;align-items:center;flex:none;padding:.5rem;height:4rem;white-space:nowrap;box-shadow:0 1px 2px grey;overflow-x:hidden;z-index:1}.dialog-buttons{display:flex;padding:.5rem;justify-content:flex-end}.dialog-buttons button{margin:0 .5rem}.avatar-box{position:relative;flex-grow:0;flex-shrink:0}.bot-form{display:flex;flex-direction:column;overflow:hidden;min-width:8rem;line-height:2rem;padding:.35rem}.bot-form div{min-height:2rem;vertical-align:middle}.bot-form button{display:inline-block;margin:.15rem;width:100%;background-color:#eef;color:#2196f3;font-size:inherit;text-transform:none;height:2rem;line-height:2rem}.bot-form>div>button{width:auto}.bot-form button:active,.bot-form button:focus,.bot-form button:hover{outline:0;background-color:#e3f2fd}#side-caption-panel{background-color:#2196f3;color:#bbdefb}#side-caption-panel a{color:#bbdefb;padding-left:.5rem;line-height:1}#side-caption-panel :hover{color:#fff}#sidepanel-title{margin-right:auto;margin-left:.5rem}#self-avatar{width:3rem;min-width:3rem;height:3rem;flex:none;border-radius:50%}#self-avatar.avatar-box{font-size:1.8rem}#settings-form ul{list-style:none}#settings-form li{vertical-align:middle}#settings-form label{display:inline-block;padding-left:.5rem}input.search{text-indent:1.4rem}i.search{position:absolute;left:.6rem}.info-box{position:relative;display:none;overflow:hidden;padding:1rem 1.5rem 1rem .5rem}.info-box .cancel{position:absolute;right:.25rem;top:.5rem}.info-box.error{display:flex;background-color:#ffcdd2;border-bottom:1px solid #ffbfc8;color:#b71c1c}.info-box.warning{display:flex;background-color:#fff9c4;border-bottom:1px solid #fff59d;color:#6d4c41}.info-box.info{display:flex;background-color:#e1f5fe;border-bottom:1px solid #b3e5fc;color:#0288d1}.info-box .cancel .material-icons{font-size:1rem;color:#757575}.info-box.error .cancel .material-icons{color:#b71c1c}.info-box .icon{display:block;margin-right:.5rem}.info-box .icon .material-icons{font-size:1.5rem}.info-box.warning .icon .material-icons{color:#fbc02d}.info-box.error .icon .material-icons{color:#ef5350}.info-box.info .icon .material-icons{color:#0288d1}.flex-column{display:flex;flex-direction:column;height:100%;overflow:hidden}.scrollable-panel{overflow-y:auto;margin-top:.25rem;margin-bottom:auto;height:100%}.contact-box{list-style:none;overflow-x:hidden}.contact-box>li{display:flex;position:relative;padding:.35rem .25rem .5rem .35rem;min-height:3.5rem;flex-grow:0;flex-shrink:0;border-bottom:1px solid #eee;border-collapse:collapse;overflow-x:hidden}.contact-box>li:last-child{border-bottom:none}.contact-box>li:hover{background-color:#eceff1}.contact-box>li.selected{background-color:#cfd8dc}.contact-box>li.action{min-height:2.5rem;border-bottom:none}.contact-box .text-box{overflow-x:hidden}.contact-box .contact-title{font-size:1.05rem;text-overflow:ellipsis;overflow:hidden}.contact-box .contact-comment{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;font-weight:lighter}.contact-box .avatar-box{margin-right:.35rem;position:relative;width:3rem;min-width:3rem;height:3rem;min-height:3rem;font-size:1.8rem;overflow:hidden;padding:0}.contact-box .avatar{width:3rem;height:3rem;border-radius:50%}.contact-box .avatar-box>.material-icons{display:flex;flex-direction:column;justify-content:space-around;font-size:2.4rem;line-height:3rem}.contact-box .text-box{display:flex;flex-direction:column;justify-content:space-around}.contact-box .action-text{text-align:center;font-size:.95rem;line-height:2.5rem;margin:0 auto;cursor:pointer}.avatar-box .online,.offline{display:inline-block;width:.75rem;min-width:.75rem;height:.75rem;min-height:.75rem;flex-shrink:0;border-radius:50%;position:absolute;right:0;bottom:.1rem}.avatar-box .online{background-color:#4caf50}.avatar-box .offline{background-color:silver}.online.typing{animation:typing .5s infinite}@keyframes typing{0%,to{transform:translateX(0)}50%{transform:translateX(-.3rem)}}.contact-box .checkmark.material-icons{display:inline-block;width:1.5rem;height:1.5rem;font-size:1.5rem;line-height:1.5rem;color:#0097a7;position:absolute;right:.05rem;bottom:.05rem;background-color:#fff;overflow:visible;border:0 solid transparent}#add-topic{position:absolute;right:1rem;bottom:1rem}.panel-form{display:flex;flex-direction:column;padding:0}.panel-form-row{align-items:center}.panel-form-column,.panel-form-row{display:flex;padding:.5rem .75rem;line-height:1.5;flex-grow:0;flex-shrink:0}.panel-form-column{flex-direction:column}.panel-form-column>.panel-form-column,.panel-form-row>.panel-form-column{padding:0!important;flex-grow:1}.panel-form-column>.panel-form-row,.panel-form-row>.panel-form-row{padding:0!important}.avatar-upload{display:flex;position:relative;flex-direction:column;flex-shrink:0;width:7rem;height:9rem;margin-left:.5rem;font-size:4.5rem;padding:0}.avatar-upload .clear-avatar{position:absolute;font-size:1rem;top:0;right:0;opacity:0;background-image:radial-gradient(#fff 40%,transparent 75%)}.avatar-upload:hover .clear-avatar{opacity:1}.avatar-upload .avatar-box{position:relative;width:7rem;height:7rem}.avatar-upload .avatar-box>.material-icons{position:relative;font-size:4rem;line-height:7rem;vertical-align:middle}.avatar-upload:after{content:".";display:block;height:0;clear:both;visibility:hidden}.avatar-upload .blank{text-align:center;vertical-align:middle;border:1px dashed #999;font-size:1rem;flex-shrink:0;width:7rem;height:7rem;overflow:hidden;color:#999;line-height:7rem;border-radius:50%}.avatar-upload .preview{width:7rem;height:7rem;overflow:hidden;border-radius:50%}.avatar-upload .inputfile.hidden{width:.1px;height:.1px;opacity:0;overflow:hidden;position:absolute}.avatar-upload .inputfile+label.round{display:flex;align-items:center;justify-content:center;cursor:pointer;width:3rem;height:3rem;line-height:3rem;border-radius:50%;box-shadow:0 1px 4px rgba(0,0,0,.4);margin-left:auto;margin-right:auto;margin-top:-1rem;bottom:0;background-color:#fafafa}.avatar-upload .inputfile:focus+label.round{outline:none}.avatar-upload .inputfile+label:hover,.avatar-upload .inputfile:focus+label{background-color:#e3f2fd;color:#42a5f5}#topic-caption-panel{background-color:#b0bec5}#topic-caption-panel a{color:#757575;text-decoration:none}#topic-caption-panel :hover{color:#000}#topic-caption-panel .avatar-box{position:relative;width:3rem;min-width:3rem;height:3rem;font-size:1.8rem;margin-right:.35rem}#topic-caption-panel .avatar-box>.material-icons{font-size:2.4rem;line-height:3rem}#topic-title-group{overflow-x:hidden}#hide-message-view{display:block;margin-right:.35rem}#topic-users{margin-left:auto;margin-right:1rem}#topic-users .avatar-box{display:inline-block;width:1.8rem;min-width:1.8rem;height:1.8rem;min-height:1.8rem;font-size:1rem;margin-right:.2rem}#topic-users .avatar-box>.material-icons{font-size:1.4rem;line-height:1.8rem}#topic-users>span{line-height:2rem;vertical-align:top}#messages-panel{position:relative;padding:.75rem;overflow-y:scroll;background-color:#eceff1;height:100%;background-image:url(../img/ignasi_pattern_s.png)}#messages-container{position:relative;overflow-y:hidden;height:100%}#write-only-background{position:absolute;top:0;left:0;height:100%;width:100%;background-image:repeating-linear-gradient(45deg,hsla(0,0%,100%,.5),hsla(0,0%,100%,.5) .5rem,hsla(0,0%,100%,.8) 0,hsla(0,0%,100%,.8) 1rem)}#write-only-note{top:50%;transform:translate(-50%,-50%);height:auto;width:auto}#peer-messaging-disabled-note,#write-only-note{position:absolute;left:50%;background-color:#fff;color:#666;line-height:2rem;text-align:center;padding:.75rem 1.25rem;border-radius:.5rem;box-shadow:0 1px .5px rgba(0,0,0,.12)}#peer-messaging-disabled-note{bottom:4.3rem;transform:translate(-50%);width:fit-content}#send-message-panel{display:flex;align-items:center;flex:none;padding:.4rem .4rem .4rem .6rem;background-color:#eceff1;box-shadow:0 -2px 3px -1px rgba(0,0,0,.1);z-index:2;height:4rem}#sendMessage{overflow:hidden;overflow-wrap:break-word}#send-message-panel .material-icons{min-width:1.75rem}#send-message-panel .material-icons.secondary,.material-icons.disabled{color:#999!important}#send-message-panel .material-icons.secondary:hover{color:#2196f3!important}.accept-invite-panel{background-color:#eceff1;box-shadow:0 -2px 3px -1px rgba(0,0,0,.1);width:100%;z-index:1}.accept-invite-panel .title{font-size:1.2rem;padding:.75rem}.accept-invite-panel .footer{background-color:#fafafa;white-space:nowrap;padding-bottom:.35rem;text-align:center}.accept-invite-panel button{font-size:1rem;height:2em;line-height:2em;border:1px solid transparent;margin:.25rem;box-shadow:0 1px 2px 0 rgba(0,0,0,.25)}#dummy-view{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background-color:#e0f7fa}#dummy-view a{text-decoration:none;display:block;text-align:center;vertical-align:middle}#image-preview{position:absolute;display:flex;flex-direction:column;top:0;left:0;width:100%;height:100%;z-index:2;background-color:hsla(0,0%,100%,.9)}#image-preview-caption-panel{display:flex;justify-content:space-between;background-color:transparent;line-height:2.5rem;height:3.5rem;padding:1rem .75rem 0}#image-preview-caption-panel a{color:#757575;text-decoration:none}#image-preview-caption-panel a:hover{color:#000}#image-preview-container{display:flex;justify-content:space-around;align-items:center;overflow:hidden;width:100%;height:100%}#image-preview-footer{display:flex;justify-content:space-around;height:3.2rem;line-height:1.5rem;background-color:transparent}.chat-box{list-style:none;padding:0 0 3.5rem;margin:0 .5rem}.chat-box.group{margin-left:-.4rem}.chat-box li{display:flex;margin:0;padding:0;align-items:flex-end}.chat-box .right{justify-content:flex-end}.chat-box .center{justify-content:center;margin-top:.5rem;margin-bottom:.5rem}.chat-box .bubble{display:flex;position:relative;max-width:40rem;min-width:9rem;height:auto;padding:.75rem .5rem .75rem 1.25rem;margin:0;text-align:left;border-radius:.5rem;box-shadow:0 1px .5px rgba(0,0,0,.12)}.chat-box li.single{margin-top:.25rem;margin-bottom:.75rem}.chat-box li.first{margin-top:.25rem;margin-bottom:.15rem}.chat-box li.middle{margin-top:.15rem;margin-bottom:.15rem}.chat-box li.last{margin-top:.15rem;margin-bottom:.75rem}.chat-box .left .bubble{background-color:#c5e1a5}.chat-box .right .bubble{background-color:#fafafa}.chat-box .center .bubble{background-color:#e3f2fd;padding:.75rem .75rem .5rem}.chat-box .left .tip:before{content:"";position:absolute;width:0;height:0;right:auto;left:-.8rem;top:auto;bottom:0;color:#c5e1a5;border-color:transparent #c5e1a5 #c5e1a5 transparent;border-style:solid;border-width:.62rem;box-shadow:0 1.6px .5px -.8px rgba(0,0,0,.12)}.chat-box .right .tip:before{content:"";position:absolute;display:flex;justify-content:space-between;width:0;height:0;left:auto;right:-.8rem;top:auto;bottom:0;color:#fafafa;border-color:transparent transparent #fafafa #fafafa;border-style:solid;border-width:.62rem;box-shadow:0 1.6px .5px -.8px rgba(0,0,0,.12)}.chat-box .avatar-box{position:relative;width:1.4rem;min-width:1.4rem;height:1.4rem;min-height:1.4rem;font-size:.8rem;margin-bottom:.8rem;margin-right:.8rem}.chat-box .bubble div.message-content{color:#212121;margin:0;max-width:36rem;overflow-wrap:break-word}.chat-box .bubble div.message-content:after{content:".";display:block;height:0;clear:both;visibility:hidden}.chat-box .timestamp{display:inline-block;position:relative;font-size:80%;color:#777;padding-left:1rem;top:.5rem;float:right;white-space:nowrap}.chat-box .typing{display:inline-block;border-radius:.5rem;width:2rem;padding:.25rem .35rem;vertical-align:middle;text-align:center;color:#aaa;margin-top:.75rem}.chat-box .author{font-size:80%;color:#777}.inline-image{max-width:36rem;max-height:24rem;overflow:hidden;border-radius:.1rem;display:block}.attachment{display:flex}.attachment:last-of-type{margin-bottom:-.45rem}#info-caption-panel{background-color:#b0bec5}#info-caption-panel a{color:#757575;line-height:1;text-decoration:none}#info-caption-panel :hover{color:#000}#info-title{margin-right:auto}#group-manager{position:relative;display:flex;flex-direction:column;height:100%}#group-manager-buttons{display:flex;background-color:#eceff1;box-shadow:0 -1px rgba(0,0,0,.1);z-index:2;justify-content:space-around;padding:.4rem;height:4rem;border-left:1px solid #ccc}ul.tabbar{flex-shrink:0;flex-grow:0;background:#eee;list-style:none;margin:0;width:100%;overflow:hidden;padding:0 0 0 1rem}ul.tabbar>li{float:left}ul.tabbar>li>a{position:relative;padding-top:.75rem;padding-left:1rem;padding-right:1rem;display:block;text-align:center;line-height:2rem;font-weight:400;font-size:1.3rem;font-variant:small-caps;text-decoration:none;color:rgba(33,150,243,.6);overflow:hidden;box-shadow:inset 0 -2px 2.5px -1px rgba(0,0,0,.25)}ul.tabbar li.active>a{color:#2196f3}ul.tabbar li.active>a:after{height:.15rem;display:block;content:"";bottom:0;left:0;position:absolute;background:#2196f3;animation:tabbar-border-expand .2s cubic-bezier(.4,0,.4,1) 0s alternate forwards}@keyframes tabbar-border-expand{0%{opacity:0;width:0}to{opacity:1;width:100%}}.unread{display:inline-block;font-size:.75rem;font-weight:bolder;background-color:#0097a7;color:#fff;border-radius:50%;width:1.35rem;height:1.35rem;margin-left:.5rem;line-height:1.35rem;text-align:center;vertical-align:middle}.material-icons.as-badge{padding-left:.2rem;font-size:1.25rem;color:#bbb}.badge{font-family:Roboto Mono,Courier,monospace,sans-serif;font-size:.8rem;border:1px solid silver;background-color:#e8e8e8;color:#666;padding:.05rem .25rem;margin:.05rem .1rem;border-radius:.2rem;display:inline-block;align-self:flex-start}.badge.green{border-color:#9c9;background-color:#efe;color:#383}.badge.yellow{border-color:#cc9;background-color:#ffe;color:#660}.badge.blue{border-color:#99c;background-color:#eef;color:#339}.badge.red{border-color:#c99;background-color:#fee;color:#933}.badge.magenta{border-color:#c9c;background-color:#fef;color:#939}.lettertile{width:100%;padding-bottom:100%;border-radius:50%}.lettertile>div{display:flex;flex-shrink:0;flex-grow:0;position:absolute;top:0;bottom:0;left:0;right:0;text-transform:uppercase;align-items:center;justify-content:center;text-align:center;border-radius:50%;line-height:1;color:hsla(0,0%,98%,.8)}.lettertile:hover{color:#fff}.light-color0{background-color:#ef9a9a}.light-color1{background-color:#90caf9}.light-color2{background-color:#b0bec5}.light-color3{background-color:#b39ddb}.light-color4{background-color:#ffab91}.light-color5{background-color:#a5d6a7}.light-color6{background-color:#eee}.light-color7{background-color:#e6ee9c}.light-color8{background-color:#c5e1a5}.light-color9{background-color:#f0e57d}.light-color10{background-color:#f48fb1}.light-color11{background-color:#9fa8da}.light-color12{background-color:#ffe082}.light-color13{background-color:#bcaaa4}.light-color14{background-color:#80deea}.light-color15{background-color:#ce93d8}.dark-color0{background-color:#c62828}.dark-color1{background-color:#ad1457}.dark-color2{background-color:#6a1b9a}.dark-color3{background-color:#4527a0}.dark-color4{background-color:#283593}.dark-color5{background-color:#1565c0}.dark-color6{background-color:#0277bd}.dark-color7{background-color:#00838f}.dark-color8{background-color:#00695c}.dark-color9{background-color:#2e7d32}.dark-color10{background-color:#558b2f}.dark-color11{background-color:#9e9d24}.dark-color12{background-color:#f9a825}.dark-color13{background-color:#ff8f00}.dark-color14{background-color:#ef6c00}.dark-color15{background-color:#d84315}.material-icons{font-size:1.4rem;line-height:1;vertical-align:middle;text-align:center}.material-icons.small{font-size:1rem}.material-icons.big{font-size:2.4rem}#self-avatar .material-icons{font-size:2.4rem;line-height:3rem}.avatar-box .material-icons{color:hsla(0,0%,98%,.8);border-radius:50%;width:inherit;height:inherit}.chip-input{display:flex;flex-wrap:wrap;flex-grow:1;cursor:text;font-size:1.1rem;font-weight:400;background:transparent;border-bottom:1px solid #ccc;box-shadow:0 1px 0 rgba(0,0,0,.1);transition:all .1s ease-in-out;max-height:10rem;min-height:4rem;overflow-y:scroll;margin:.25rem 0;outline:none;color:#666;align-items:center;justify-content:flex-start;text-align:center}.chip-input.focused{border-bottom:1px solid #2196f3;box-shadow:0 1px 0 rgba(33,150,243,.25)}.chip-input>input[type=text]{display:inline-block;width:auto;border:none;line-height:1.8;box-shadow:none;min-width:4rem;flex:1 0 auto;height:1.8rem;margin:.125rem}.chip{display:flex;flex:0 0 auto;align-items:center;height:1.8rem;line-height:1.8;border-radius:.9rem;margin:.125rem;background-color:#eee;white-space:nowrap;font-size:90%}.chip.invalid{color:#c66;background-color:#fee}.chip>a{display:inline-block;color:#ccc;background-color:#aaa;border-radius:50%;margin:.3rem;width:1.175rem;height:1.175rem;line-height:1.15;text-decoration:none}.chip>a:focus,.chip>a:hover{text-decoration:none;color:#ddd;background-color:#bbb}.chip .avatar-box{position:relative;width:1.8rem;min-width:1.8rem;height:1.8rem;min-height:1.8rem;font-size:1rem;margin-right:.2rem}.chip .avatar-box .material-icons{font-size:1.4rem;line-height:1.8rem}.chip .spacer{display:inline-block;margin:.3rem;width:.25rem;height:1.175rem}.load-spinner-box{position:absolute;box-shadow:.1rem .1rem .15rem 1px rgba(0,0,0,.25);border-radius:50%;padding:.25rem;width:2.5rem;height:2.5rem;left:0;right:0;top:5rem;margin-left:auto;margin-right:auto;background-color:#fff;z-index:2}.loader-spinner{border-radius:50%;border:.35rem solid #f3f3f3;border-top-color:#1e88e5;width:2rem;height:2rem;animation:spin 1.5s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.uploader{display:flex}.uploader>div{position:relative;height:.6rem;padding:.15rem;margin:.35rem;width:6rem;line-height:0;border-radius:.1rem;background-color:#cfd8dc}.uploader>div>span{display:inline-block;height:.3rem;background-color:#1e88e5;border-radius:.08rem}
\ No newline at end of file
+*{box-sizing:border-box;padding:0;margin:0;font-family:Roboto,Arial,sans-serif;text-rendering:optimizeLegibility}body,html{height:100%;width:100%;font-size:10pt;color:#666;overflow:hidden;background-color:silver;box-shadow:inset 0 10rem #3949ab}tt{font-family:Roboto Mono,Courier,monospace}a{color:#2196f3;text-decoration:none}a:focus,a:hover{color:#0a6ebd;text-decoration:underline}a.red:focus,a.red:hover{color:#f44336}a.flat-button{display:flex;align-items:center;margin:.15rem .35rem;height:2em;line-height:2em;font-weight:400;text-align:center;vertical-align:middle;touch-action:manipulation;white-space:nowrap;text-transform:uppercase}a.flat-button:focus,a.flat-button:hover{text-decoration:none}form{padding:.5rem .75rem;line-height:1.5}form,label{font-size:1.1rem}label{margin-right:auto}label.small{color:#1e88e5;font-size:.95rem}img.avatar{width:100%;height:100%;border-radius:50%}button{display:inline-block;font-family:inherit;font-size:1.05rem;height:2.2rem;line-height:2.2rem;font-weight:400;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;border:1px solid transparent;white-space:nowrap;padding-left:1.2rem;padding-right:1.2rem;border-radius:4px;box-shadow:0 2px 3px 0 rgba(0,0,0,.25);text-transform:uppercase;min-width:5rem;margin:0 .35rem}button[disabled],html input[disabled]{cursor:default;opacity:.65;box-shadow:none}button.round{border-radius:50%;width:3rem;height:3rem;padding:0;margin:0;line-height:3.5rem;outline:none;box-shadow:0 2px .5rem 0 rgba(0,0,0,.25);z-index:2}button.round.small{width:2rem;height:2rem;line-height:2rem;box-shadow:0 2px .35rem 0 rgba(0,0,0,.25)}button:active,button:focus,button:hover{color:#fff}button:active{box-shadow:inset 0 3px .5rem rgba(0,0,0,.125)}button:active.round.small{box-shadow:inset 0 2px .35rem rgba(0,0,0,.125)}button.blue:active,button.blue:focus,button.blue:hover{background-color:#1976d2}button.blue{background-color:#1e88e5;color:#fff}button.blue:active,button.blue:focus,button.blue:hover{background-color:#2962ff}button.white{background-color:#fff;color:#2196f3}button.blue:active,button.white:focus,button.white:hover{background-color:#e3f2fd}button.outline{background-color:transparent;color:#2196f3;border:1px solid silver;box-shadow:none}button.outline:active,button.outline:focus,button.outline:hover{background-color:#e8eaf6}input,textarea{font-family:inherit;font-size:1.1rem;background:transparent;border:none;margin:.25rem 0;padding:0 .125rem;outline:none;color:#666}textarea{resize:none}input[type=email],input[type=password],input[type=search],input[type=text],textarea{height:2.5rem;border-bottom:1px solid #ccc;box-shadow:0 1px 0 rgba(0,0,0,.1);transition:all .1s ease-in-out}input[type=email],input[type=password],input[type=text],textarea{width:100%}input[type=email]:focus,input[type=password]:focus,input[type=search]:focus,input[type=text]:focus,textarea:focus{border-bottom:1px solid #2196f3;box-shadow:0 1px 0 rgba(33,150,243,.25)}input[type=email].invalid,input[type=password].invalid,input[type=search].invalid,input[type=text].invalid,textarea.invalid{border-bottom:1px solid #e57373;box-shadow:0 1px 0 rgba(229,115,115,.25)}input[type=email]::placeholder,input[type=password]::placeholder,input[type=search]::placeholder,input[type=text]::placeholder,textarea::placeholder{color:#bbb;font-weight:lighter}input[type=password].with-visibility,input[type=text].with-visibility{margin-right:-2rem;padding-right:2rem}::-webkit-scrollbar{width:.6rem}::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.2);background-color:hsla(0,0%,100%,.1)}::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.3)}*{scrollbar-width:thin;scrollbar-color:silver}ul.menu{position:absolute;list-style:none;width:12rem;border-radius:.2rem;padding-top:.35rem;padding-bottom:.35rem;box-shadow:0 1px 6px rgba(0,0,0,.45);background-color:#fff;z-index:4}ul.menu>li{padding:.75rem .75rem .75rem 1.25rem}ul.menu>li:hover{background-color:#f5f5f5}ul.menu>li.separator{padding:0;margin:2px;height:1px;background-color:#ccc}ul.menu>li.separator:hover{background-color:#ccc}li .menuTrigger{position:absolute;top:.25rem;right:.25rem;width:1.5rem;height:1.2rem;line-height:1.2rem;border-radius:.6rem;text-align:center;opacity:0}li:hover .menuTrigger{opacity:1}li:hover .menuTrigger a{color:#757575}li.left:hover .menuTrigger{-text-shadow:#c5e1a5 0 0 15px,#c5e1a5 0 0 5px;background-image:radial-gradient(#c5e1a5 40%,transparent 75%)}li.right:hover .menuTrigger{-text-shadow:#fafafa 0 0 15px,#fafafa 0 0 5px;background-image:radial-gradient(#fafafa 40%,transparent 75%)}div.alert-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%;position:absolute;-x-backdrop-filter:blur(4px);background:rgba(0,0,0,.33);z-index:3}div.alert{background-color:#fff;box-shadow:0 1px 6px rgba(0,0,0,.45);padding:1rem;border-radius:4px;margin:auto 2rem;width:content;min-width:16rem;max-width:24rem}div.alert .title{color:#2196f3;font-size:1.2rem;font-weight:500;width:100%;padding:0 0 .35rem}div.alert .content{padding:.5rem 0}.blue{color:#2196f3}.red{color:#d32f2f}.amber{color:#ffc107}.gray{color:#757575}.light-gray{color:#bbb}.small{font-size:90%}.hr{display:block;width:100%;height:.45rem;min-height:.3rem;background-color:#f0f0f0;box-shadow:inset 0 .25rem .25rem -.25rem rgba(0,0,0,.15),inset 0 -.25rem .25rem -.25rem rgba(0,0,0,.15)}.group-focus{outline:none}#mountPoint{height:100%;padding-top:1rem;padding-bottom:3rem}#noscript-message{position:relative;display:flex;flex-direction:column;max-width:30rem;overflow:hidden;margin-top:5rem;margin-left:auto;margin-right:auto;border-radius:.25rem;box-shadow:1px 2px .3rem grey;background-color:#fff}#noscript-title{padding:1rem;font-size:120%;font-weight:bolder;background-color:#2196f3;color:#bbdefb}#noscript-content{padding:1rem}#app-container{position:relative;display:flex;height:100%;max-width:80rem;overflow:hidden;margin-left:auto;margin-right:auto;border-radius:.25rem;box-shadow:1px 2px .3rem grey;background-color:#fff}#sidepanel{flex-grow:0;flex-shrink:0.3;width:24rem;min-width:18rem}#sidepanel,#topic-view{position:relative;display:flex;flex-direction:column;height:100%}#topic-view{overflow-x:hidden;width:100%}#info-view{display:flex;flex-direction:column;flex-grow:0;flex-shrink:0.3;width:24rem;min-width:18rem;height:100%}@media (max-width:960px){#mountPoint{padding:0}}@media (max-width:640px){#app-container{width:100%;border-radius:0;box-shadow:none}#info-view,#sidepanel{width:100%}}.center-medium-text{width:100%;height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-size:1.4rem}.nodisplay{display:none!important}.quoted{margin-left:1rem}.in-place-edit{display:inline-block;font-family:inherit;font-size:1.1rem;height:2.5rem;line-height:2.25;margin:.25rem .125rem;width:100%;vertical-align:baseline;border-bottom:1px dashed #ddd;cursor:pointer}.in-place-edit.disabled{border-bottom:none;cursor:default}.in-place-edit.placeholder{color:#bbb}.in-place-edit .content{display:block;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}table.permission-editor{width:100%}table.permission-editor td{margin:.35rem 0;font-size:1.1rem;vertical-align:middle}table.permission-editor td.checkbox{text-align:center;color:#999}.permission-editor .material-icons{font-size:1.1rem;line-height:1.1rem}.material-icons.clickable{cursor:pointer;border-bottom:none}.clickable{border-bottom:1px dashed #ddd}.clean-clickable,.clickable,.image-clickable{cursor:pointer}.panel-title{font-size:1.4rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.caption-panel{display:flex;align-items:center;flex:none;padding:.5rem;height:4rem;white-space:nowrap;box-shadow:0 1px 2px grey;overflow-x:hidden;z-index:1}.dialog-buttons{display:flex;padding:.5rem;justify-content:flex-end}.dialog-buttons button{margin:0 .5rem}.avatar-box{position:relative;flex-grow:0;flex-shrink:0}.bot-form{display:flex;flex-direction:column;overflow:hidden;min-width:8rem;line-height:2rem;padding:.35rem}.bot-form div{min-height:2rem;vertical-align:middle}.bot-form button{display:inline-block;margin:.15rem;width:100%;background-color:#eef;color:#2196f3;font-size:inherit;text-transform:none;height:2rem;line-height:2rem}.bot-form>div>button{width:auto}.bot-form button:active,.bot-form button:focus,.bot-form button:hover{outline:0;background-color:#e3f2fd}#side-caption-panel{background-color:#2196f3;color:#bbdefb}#side-caption-panel a{color:#bbdefb;padding-left:.5rem;line-height:1}#side-caption-panel :hover{color:#fff}#sidepanel-title{margin-right:auto;margin-left:.5rem}#self-avatar{width:3rem;min-width:3rem;height:3rem;flex:none;border-radius:50%}#self-avatar.avatar-box{font-size:1.8rem}#settings-form ul{list-style:none}#settings-form li{vertical-align:middle}#settings-form label{display:inline-block;padding-left:.5rem}input.search{text-indent:1.4rem}i.search{position:absolute;left:.6rem}.info-box{position:relative;display:none;overflow:hidden;padding:1rem 1.5rem 1rem .5rem}.info-box .cancel{position:absolute;right:.25rem;top:.5rem}.info-box.error{display:flex;background-color:#ffcdd2;border-bottom:1px solid #ffbfc8;color:#b71c1c}.info-box.warning{display:flex;background-color:#fff9c4;border-bottom:1px solid #fff59d;color:#6d4c41}.info-box.info{display:flex;background-color:#e1f5fe;border-bottom:1px solid #b3e5fc;color:#0288d1}.info-box .cancel .material-icons{font-size:1rem;color:#757575}.info-box.error .cancel .material-icons{color:#b71c1c}.info-box .icon{display:block;margin-right:.5rem}.info-box .icon .material-icons{font-size:1.5rem}.info-box.warning .icon .material-icons{color:#fbc02d}.info-box.error .icon .material-icons{color:#ef5350}.info-box.info .icon .material-icons{color:#0288d1}.flex-column{display:flex;flex-direction:column;height:100%;overflow:hidden}.scrollable-panel{overflow-y:auto;margin-top:.25rem;margin-bottom:auto;height:100%}.contact-box{list-style:none;overflow-x:hidden}.contact-box>li{display:flex;position:relative;padding:.35rem .25rem .5rem .35rem;min-height:3.5rem;flex-grow:0;flex-shrink:0;border-bottom:1px solid #eee;border-collapse:collapse;overflow-x:hidden}.contact-box>li:last-child{border-bottom:none}.contact-box>li:hover{background-color:#eceff1}.contact-box>li.selected{background-color:#cfd8dc}.contact-box>li.action{min-height:2.5rem;border-bottom:none}.contact-box .text-box{overflow-x:hidden}.contact-box .contact-title{font-size:1.05rem;text-overflow:ellipsis;overflow:hidden}.contact-box .contact-comment{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;font-weight:lighter}.contact-box .avatar-box{margin-right:.35rem;position:relative;width:3rem;min-width:3rem;height:3rem;min-height:3rem;font-size:1.8rem;overflow:hidden;padding:0}.contact-box .avatar{width:3rem;height:3rem;border-radius:50%}.contact-box .avatar-box>.material-icons{display:flex;flex-direction:column;justify-content:space-around;font-size:2.4rem;line-height:3rem}.contact-box .text-box{display:flex;flex-direction:column;justify-content:space-around}.contact-box .action-text{text-align:center;font-size:.95rem;line-height:2.5rem;margin:0 auto;cursor:pointer}.avatar-box .online,.offline{display:inline-block;width:.75rem;min-width:.75rem;height:.75rem;min-height:.75rem;flex-shrink:0;border-radius:50%;position:absolute;right:0;bottom:.1rem}.avatar-box .online{background-color:#4caf50}.avatar-box .offline{background-color:silver}.online.typing{animation:typing .5s infinite}@keyframes typing{0%,to{transform:translateX(0)}50%{transform:translateX(-.3rem)}}.contact-box .checkmark.material-icons{display:inline-block;width:1.5rem;height:1.5rem;font-size:1.5rem;line-height:1.5rem;color:#0097a7;position:absolute;right:.05rem;bottom:.05rem;background-color:#fff;overflow:visible;border:0 solid transparent}#add-topic{position:absolute;right:1rem;bottom:1rem}.panel-form{display:flex;flex-direction:column;padding:0}.panel-form-row{align-items:center}.panel-form-column,.panel-form-row{display:flex;padding:.5rem .75rem;line-height:1.5;flex-grow:0;flex-shrink:0}.panel-form-column{flex-direction:column}.panel-form-column>.panel-form-column,.panel-form-row>.panel-form-column{padding:0!important;flex-grow:1}.panel-form-column>.panel-form-row,.panel-form-row>.panel-form-row{padding:0!important}.avatar-upload{display:flex;position:relative;flex-direction:column;flex-shrink:0;width:7rem;height:9rem;margin-left:.5rem;font-size:4.5rem;padding:0}.avatar-upload .clear-avatar{position:absolute;font-size:1rem;top:0;right:0;opacity:0;background-image:radial-gradient(#fff 40%,transparent 75%)}.avatar-upload:hover .clear-avatar{opacity:1}.avatar-upload .avatar-box{position:relative;width:7rem;height:7rem}.avatar-upload .avatar-box>.material-icons{position:relative;font-size:4rem;line-height:7rem;vertical-align:middle}.avatar-upload:after{content:".";display:block;height:0;clear:both;visibility:hidden}.avatar-upload .blank{text-align:center;vertical-align:middle;border:1px dashed #999;font-size:1rem;flex-shrink:0;width:7rem;height:7rem;overflow:hidden;color:#999;line-height:7rem;border-radius:50%}.avatar-upload .preview{width:7rem;height:7rem;overflow:hidden;border-radius:50%}.avatar-upload .inputfile.hidden{width:.1px;height:.1px;opacity:0;overflow:hidden;position:absolute}.avatar-upload .inputfile+label.round{display:flex;align-items:center;justify-content:center;cursor:pointer;width:3rem;height:3rem;line-height:3rem;border-radius:50%;box-shadow:0 1px 4px rgba(0,0,0,.4);margin-left:auto;margin-right:auto;margin-top:-1rem;bottom:0;background-color:#fafafa}.avatar-upload .inputfile:focus+label.round{outline:none}.avatar-upload .inputfile+label:hover,.avatar-upload .inputfile:focus+label{background-color:#e3f2fd;color:#42a5f5}#topic-caption-panel{background-color:#b0bec5}#topic-caption-panel a{color:#757575;text-decoration:none}#topic-caption-panel :hover{color:#000}#topic-caption-panel .avatar-box{position:relative;width:3rem;min-width:3rem;height:3rem;font-size:1.8rem;margin-right:.35rem}#topic-caption-panel .avatar-box>.material-icons{font-size:2.4rem;line-height:3rem}#topic-title-group{overflow-x:hidden}#hide-message-view{display:block;margin-right:.35rem}#topic-users{margin-left:auto;margin-right:1rem}#topic-users .avatar-box{display:inline-block;width:1.8rem;min-width:1.8rem;height:1.8rem;min-height:1.8rem;font-size:1rem;margin-right:.2rem}#topic-users .avatar-box>.material-icons{font-size:1.4rem;line-height:1.8rem}#topic-users>span{line-height:2rem;vertical-align:top}#messages-panel{position:relative;padding:.75rem;overflow-y:scroll;background-color:#eceff1;height:100%;background-image:url(../img/ignasi_pattern_s.png)}#messages-container{position:relative;overflow-y:hidden;height:100%}#write-only-background{position:absolute;top:0;left:0;height:100%;width:100%;background-image:repeating-linear-gradient(45deg,hsla(0,0%,100%,.5),hsla(0,0%,100%,.5) .5rem,hsla(0,0%,100%,.8) 0,hsla(0,0%,100%,.8) 1rem)}#write-only-note{top:50%;transform:translate(-50%,-50%);height:auto;width:auto}#peer-messaging-disabled-note,#write-only-note{position:absolute;left:50%;background-color:#fff;color:#666;line-height:2rem;text-align:center;padding:.75rem 1.25rem;border-radius:.5rem;box-shadow:0 1px .5px rgba(0,0,0,.12)}#peer-messaging-disabled-note{bottom:4.3rem;transform:translate(-50%);width:fit-content}#send-message-panel{display:flex;align-items:center;flex:none;padding:.4rem .4rem .4rem .6rem;background-color:#eceff1;box-shadow:0 -2px 3px -1px rgba(0,0,0,.1);z-index:2;height:4rem}#sendMessage{overflow:hidden;overflow-wrap:break-word}#send-message-panel .material-icons{min-width:1.75rem}#send-message-panel .material-icons.secondary,.material-icons.disabled{color:#999!important}#send-message-panel .material-icons.secondary:hover{color:#2196f3!important}.accept-invite-panel{background-color:#eceff1;box-shadow:0 -2px 3px -1px rgba(0,0,0,.1);width:100%;z-index:1}.accept-invite-panel .title{font-size:1.2rem;padding:.75rem}.accept-invite-panel .footer{background-color:#fafafa;white-space:nowrap;padding-bottom:.35rem;text-align:center}.accept-invite-panel button{font-size:1rem;height:2em;line-height:2em;border:1px solid transparent;margin:.25rem;box-shadow:0 1px 2px 0 rgba(0,0,0,.25)}#dummy-view{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background-color:#e0f7fa}#dummy-view a{text-decoration:none;display:block;text-align:center;vertical-align:middle}#image-preview{position:absolute;display:flex;flex-direction:column;top:0;left:0;width:100%;height:100%;z-index:2;background-color:hsla(0,0%,100%,.9)}#image-preview-caption-panel{display:flex;justify-content:space-between;background-color:#eceff1;box-shadow:0 1px 2px silver;line-height:2.5rem;height:4.6rem;padding:1rem .75rem 0;z-index:3}#image-preview-caption-panel a{color:#757575;text-decoration:none}#image-preview-caption-panel a:hover{color:#000}#image-preview-container{display:flex;justify-content:space-around;align-items:center;overflow:hidden;width:100%;height:100%}#image-preview-footer{display:flex;justify-content:space-around;height:3.2rem;line-height:1.5rem;background-color:#eceff1;box-shadow:0 -2px 3px -1px rgba(0,0,0,.1)}.chat-box{list-style:none;padding:0 0 3.5rem;margin:0 .5rem}.chat-box.group{margin-left:-.4rem}.chat-box li{display:flex;margin:0;padding:0;align-items:flex-end}.chat-box .right{justify-content:flex-end}.chat-box .center{justify-content:center;margin-top:.5rem;margin-bottom:.5rem}.chat-box .bubble{display:flex;position:relative;max-width:40rem;min-width:9rem;height:auto;padding:.75rem .5rem .75rem 1.25rem;margin:0;text-align:left;border-radius:.5rem;box-shadow:0 1px .5px rgba(0,0,0,.12)}.chat-box li.single{margin-top:.25rem;margin-bottom:.75rem}.chat-box li.first{margin-top:.25rem;margin-bottom:.15rem}.chat-box li.middle{margin-top:.15rem;margin-bottom:.15rem}.chat-box li.last{margin-top:.15rem;margin-bottom:.75rem}.chat-box .left .bubble{background-color:#c5e1a5}.chat-box .right .bubble{background-color:#fafafa}.chat-box .center .bubble{background-color:#e3f2fd;padding:.75rem .75rem .5rem}.chat-box .left .tip:before{content:"";position:absolute;width:0;height:0;right:auto;left:-.8rem;top:auto;bottom:0;color:#c5e1a5;border-color:transparent #c5e1a5 #c5e1a5 transparent;border-style:solid;border-width:.62rem;box-shadow:0 1.6px .5px -.8px rgba(0,0,0,.12)}.chat-box .right .tip:before{content:"";position:absolute;display:flex;justify-content:space-between;width:0;height:0;left:auto;right:-.8rem;top:auto;bottom:0;color:#fafafa;border-color:transparent transparent #fafafa #fafafa;border-style:solid;border-width:.62rem;box-shadow:0 1.6px .5px -.8px rgba(0,0,0,.12)}.chat-box .avatar-box{position:relative;width:1.4rem;min-width:1.4rem;height:1.4rem;min-height:1.4rem;font-size:.8rem;margin-bottom:.8rem;margin-right:.8rem}.chat-box .bubble div.message-content{color:#212121;margin:0;max-width:36rem;overflow-wrap:break-word}.chat-box .bubble div.message-content:after{content:".";display:block;height:0;clear:both;visibility:hidden}.chat-box .timestamp{display:inline-block;position:relative;font-size:80%;color:#777;padding-left:1rem;top:.5rem;float:right;white-space:nowrap}.chat-box .typing{display:inline-block;border-radius:.5rem;width:2rem;padding:.25rem .35rem;vertical-align:middle;text-align:center;color:#aaa;margin-top:.75rem}.chat-box .author{font-size:80%;color:#777}.inline-image{max-width:36rem;max-height:24rem;overflow:hidden;border-radius:.1rem;display:block}.attachment{display:flex}.attachment:last-of-type{margin-bottom:-.45rem}#info-caption-panel{background-color:#b0bec5}#info-caption-panel a{color:#757575;line-height:1;text-decoration:none}#info-caption-panel :hover{color:#000}#info-title{margin-right:auto}#group-manager{position:relative;display:flex;flex-direction:column;height:100%}#group-manager-buttons{display:flex;background-color:#eceff1;box-shadow:0 -1px rgba(0,0,0,.1);z-index:2;justify-content:space-around;padding:.4rem;height:4rem;border-left:1px solid #ccc}ul.tabbar{flex-shrink:0;flex-grow:0;background:#eee;list-style:none;margin:0;width:100%;overflow:hidden;padding:0 0 0 1rem}ul.tabbar>li{float:left}ul.tabbar>li>a{position:relative;padding-top:.75rem;padding-left:1rem;padding-right:1rem;display:block;text-align:center;line-height:2rem;font-weight:400;font-size:1.3rem;font-variant:small-caps;text-decoration:none;color:rgba(33,150,243,.6);overflow:hidden;box-shadow:inset 0 -2px 2.5px -1px rgba(0,0,0,.25)}ul.tabbar li.active>a{color:#2196f3}ul.tabbar li.active>a:after{height:.15rem;display:block;content:"";bottom:0;left:0;position:absolute;background:#2196f3;animation:tabbar-border-expand .2s cubic-bezier(.4,0,.4,1) 0s alternate forwards}@keyframes tabbar-border-expand{0%{opacity:0;width:0}to{opacity:1;width:100%}}.unread{display:inline-block;font-size:.75rem;font-weight:bolder;background-color:#0097a7;color:#fff;border-radius:50%;width:1.35rem;height:1.35rem;margin-left:.5rem;line-height:1.35rem;text-align:center;vertical-align:middle}.material-icons.as-badge{padding-left:.2rem;font-size:1.25rem;color:#bbb}.badge{font-family:Roboto Mono,Courier,monospace,sans-serif;font-size:.8rem;border:1px solid silver;background-color:#e8e8e8;color:#666;padding:.05rem .25rem;margin:.05rem .1rem;border-radius:.2rem;display:inline-block;align-self:flex-start}.badge.green{border-color:#9c9;background-color:#efe;color:#383}.badge.yellow{border-color:#cc9;background-color:#ffe;color:#660}.badge.blue{border-color:#99c;background-color:#eef;color:#339}.badge.red{border-color:#c99;background-color:#fee;color:#933}.badge.magenta{border-color:#c9c;background-color:#fef;color:#939}.lettertile{width:100%;padding-bottom:100%;border-radius:50%}.lettertile>div{display:flex;flex-shrink:0;flex-grow:0;position:absolute;top:0;bottom:0;left:0;right:0;text-transform:uppercase;align-items:center;justify-content:center;text-align:center;border-radius:50%;line-height:1;color:hsla(0,0%,98%,.8)}.lettertile:hover{color:#fff}.light-color0{background-color:#ef9a9a}.light-color1{background-color:#90caf9}.light-color2{background-color:#b0bec5}.light-color3{background-color:#b39ddb}.light-color4{background-color:#ffab91}.light-color5{background-color:#a5d6a7}.light-color6{background-color:#eee}.light-color7{background-color:#e6ee9c}.light-color8{background-color:#c5e1a5}.light-color9{background-color:#f0e57d}.light-color10{background-color:#f48fb1}.light-color11{background-color:#9fa8da}.light-color12{background-color:#ffe082}.light-color13{background-color:#bcaaa4}.light-color14{background-color:#80deea}.light-color15{background-color:#ce93d8}.dark-color0{background-color:#c62828}.dark-color1{background-color:#ad1457}.dark-color2{background-color:#6a1b9a}.dark-color3{background-color:#4527a0}.dark-color4{background-color:#283593}.dark-color5{background-color:#1565c0}.dark-color6{background-color:#0277bd}.dark-color7{background-color:#00838f}.dark-color8{background-color:#00695c}.dark-color9{background-color:#2e7d32}.dark-color10{background-color:#558b2f}.dark-color11{background-color:#9e9d24}.dark-color12{background-color:#f9a825}.dark-color13{background-color:#ff8f00}.dark-color14{background-color:#ef6c00}.dark-color15{background-color:#d84315}.material-icons{font-size:1.4rem;line-height:1;vertical-align:middle;text-align:center}.material-icons.small{font-size:1rem}.material-icons.big{font-size:2.4rem}#self-avatar .material-icons{font-size:2.4rem;line-height:3rem}.avatar-box .material-icons{color:hsla(0,0%,98%,.8);border-radius:50%;width:inherit;height:inherit}.chip-input{display:flex;flex-wrap:wrap;flex-grow:1;cursor:text;font-size:1.1rem;font-weight:400;background:transparent;border-bottom:1px solid #ccc;box-shadow:0 1px 0 rgba(0,0,0,.1);transition:all .1s ease-in-out;max-height:10rem;min-height:4rem;overflow-y:scroll;margin:.25rem 0;outline:none;color:#666;align-items:center;justify-content:flex-start;text-align:center}.chip-input.focused{border-bottom:1px solid #2196f3;box-shadow:0 1px 0 rgba(33,150,243,.25)}.chip-input>input[type=text]{display:inline-block;width:auto;border:none;line-height:1.8;box-shadow:none;min-width:4rem;flex:1 0 auto;height:1.8rem;margin:.125rem}.chip{display:flex;flex:0 0 auto;align-items:center;height:1.8rem;line-height:1.8;border-radius:.9rem;margin:.125rem;background-color:#eee;white-space:nowrap;font-size:90%}.chip.invalid{color:#c66;background-color:#fee}.chip>a{display:inline-block;color:#ccc;background-color:#aaa;border-radius:50%;margin:.3rem;width:1.175rem;height:1.175rem;line-height:1.15;text-decoration:none}.chip>a:focus,.chip>a:hover{text-decoration:none;color:#ddd;background-color:#bbb}.chip .avatar-box{position:relative;width:1.8rem;min-width:1.8rem;height:1.8rem;min-height:1.8rem;font-size:1rem;margin-right:.2rem}.chip .avatar-box .material-icons{font-size:1.4rem;line-height:1.8rem}.chip .spacer{display:inline-block;margin:.3rem;width:.25rem;height:1.175rem}.load-spinner-box{position:absolute;box-shadow:.1rem .1rem .15rem 1px rgba(0,0,0,.25);border-radius:50%;padding:.25rem;width:2.5rem;height:2.5rem;left:0;right:0;top:5rem;margin-left:auto;margin-right:auto;background-color:#fff;z-index:2}.loader-spinner{border-radius:50%;border:.35rem solid #f3f3f3;border-top-color:#1e88e5;width:2rem;height:2rem;animation:spin 1.5s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.uploader{display:flex}.uploader>div{position:relative;height:.6rem;padding:.15rem;margin:.35rem;width:6rem;line-height:0;border-radius:.1rem;background-color:#cfd8dc}.uploader>div>span{display:inline-block;height:.3rem;background-color:#1e88e5;border-radius:.08rem}
\ No newline at end of file
diff --git a/src/views/messages-view.jsx b/src/views/messages-view.jsx
index bdeaa1ab..3f06002b 100644
--- a/src/views/messages-view.jsx
+++ b/src/views/messages-view.jsx
@@ -14,8 +14,8 @@ import LoadSpinner from '../widgets/load-spinner.jsx';
import LogoView from './logo-view.jsx';
import SendMessage from '../widgets/send-message.jsx';
-import { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE } from '../config.js';
-import { makeImageUrl } from '../lib/blob-helpers.js';
+import { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';
+import { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64, imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js';
import { shortDateFormat } from '../lib/strformat.js';
const messages = defineMessages({
@@ -77,6 +77,7 @@ class MessagesView extends React.Component {
this.handleEnablePeer = this.handleEnablePeer.bind(this);
this.handleAttachFile = this.handleAttachFile.bind(this);
this.handleAttachImage = this.handleAttachImage.bind(this);
+ this.handleNewImagePreview = this.handleNewImagePreview.bind(this);
}
componentDidMount() {
@@ -505,177 +506,231 @@ class MessagesView extends React.Component {
}
}
- handleAttachImage(mime, bits, width, height, fname) {
- this.props.sendMessage(Drafty.insertImage(null, 0, mime, bits, width, height, fname));
+ handleNewImagePreview(content) {
+ this.setState({ imagePreview: content });
+ // this.props.sendMessage(Drafty.insertImage(null, 0, mime, bits, width, height, fname));
+ }
+
+ handleAttachImage(file) {
+ // Check if the uploaded file is indeed an image and if it isn't too large.
+ if (file.size > MAX_INBAND_ATTACHMENT_SIZE || SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) {
+ // Convert image for size or format.
+ imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false,
+ // Success
+ (bits, mime, width, height, fname) => {
+ this.handleNewImagePreview({
+ url: URL.createObjectURL(file),
+ filename: fname,
+ width: width,
+ height: height,
+ size: bits.length,
+ type: mime
+ });
+ },
+ // Failure
+ (err) => {
+ this.props.onError(err, 'err');
+ });
+ } else {
+ // Image can be uploaded as is. No conversion is needed.
+ imageFileToBase64(file,
+ // Success
+ (bits, mime, width, height, fname) => {
+ this.handleNewImagePreview({
+ url: URL.createObjectURL(file),
+ filename: fname,
+ width: width,
+ height: height,
+ size: bits.length,
+ type: mime
+ });
+ },
+ // Failure
+ (err) => {
+ this.props.onError(err, 'err');
+ }
+ );
+ }
}
render() {
const {formatMessage} = this.props.intl;
let component;
- if (this.state.topic) {
- const topic = this.props.tinode.getTopic(this.state.topic);
- const groupTopic = topic.getType() == 'grp';
- let messageNodes = [];
- let previousFrom = null;
- let chatBoxClass = null;
- for (let i=0; i
- );
- }
-
- let lastSeen = null;
- const cont = this.props.tinode.getMeTopic().getContact(this.state.topic);
- if (cont && Tinode.topicType(cont.topic) == 'p2p') {
- if (cont.online) {
- lastSeen = formatMessage(messages.online_now);
- } else if (cont.seen) {
- lastSeen = formatMessage(messages.last_seen) + ": " +
- shortDateFormat(cont.seen.when, this.props.intl.locale);
- // TODO: also handle user agent in c.seen.ua
- }
- }
- const avatar = this.state.avatar || true;
- const online = this.props.online ? 'online' + (this.state.typingIndicator ? ' typing' : '') : 'offline';
-
- component = (
-
-
- {this.props.displayMobile ?
-
- : null}
-
-
-
- {!this.state.isReader ?
-
- {this.state.readingBlocked ?
-
-
-
- : null }
-
- : null }
-
- {this.state.peerMessagingDisabled && !this.state.unconfirmed ?
-
: null}
- {this.state.unconfirmed ?
-
- :
-
}
- {this.state.imagePreview ?
-
: null}
-
- );
- } else {
+ if (this.props.hideSelf) {
+ component = null;
+ } else if (!this.state.topic) {
component = (
);
+ } else {
+ let component2;
+ if (this.state.imagePreview) {
+ component2 = (
+
+ );
+ } else {
+ const topic = this.props.tinode.getTopic(this.state.topic);
+ const groupTopic = topic.getType() == 'grp';
+ let messageNodes = [];
+ let previousFrom = null;
+ let chatBoxClass = null;
+ for (let i=0; i
+ );
+ }
+
+ let lastSeen = null;
+ const cont = this.props.tinode.getMeTopic().getContact(this.state.topic);
+ if (cont && Tinode.topicType(cont.topic) == 'p2p') {
+ if (cont.online) {
+ lastSeen = formatMessage(messages.online_now);
+ } else if (cont.seen) {
+ lastSeen = formatMessage(messages.last_seen) + ": " +
+ shortDateFormat(cont.seen.when, this.props.intl.locale);
+ // TODO: also handle user agent in c.seen.ua
+ }
+ }
+ const avatar = this.state.avatar || true;
+ const online = this.props.online ? 'online' + (this.state.typingIndicator ? ' typing' : '') : 'offline';
+
+ component2 = (
+ <>
+
+ {this.props.displayMobile ?
+
+ : null}
+
+
+
+ {!this.state.isReader ?
+
+ {this.state.readingBlocked ?
+
+
+
+ : null }
+
+ : null }
+
+ {this.state.peerMessagingDisabled && !this.state.unconfirmed ?
+ : null}
+ {this.state.unconfirmed ?
+
+ :
+ }
+ >
+ );
+ }
+
+ component = {component2}
}
return component;
}
diff --git a/src/widgets/chat-message.jsx b/src/widgets/chat-message.jsx
index b134eccb..c45f5655 100644
--- a/src/widgets/chat-message.jsx
+++ b/src/widgets/chat-message.jsx
@@ -20,13 +20,13 @@ export default class ChatMessage extends React.Component {
props.uploader.onProgress = this.handleProgress.bind(this);
}
- this.handlePreviewImage = this.handlePreviewImage.bind(this);
+ this.handleImagePreview = this.handleImagePreview.bind(this);
this.handleFormButtonClick = this.handleFormButtonClick.bind(this);
this.handleContextClick = this.handleContextClick.bind(this);
this.handleCancelUpload = this.handleCancelUpload.bind(this);
}
- handlePreviewImage(e) {
+ handleImagePreview(e) {
e.preventDefault();
this.props.onImagePreview({
url: e.target.src,
@@ -177,7 +177,7 @@ function draftyFormatter(style, data, values, key) {
attr.style = { width: dim.dstWidth + 'px', height: dim.dstHeight + 'px' };
attr.src = sanitizeImageUrl(attr.src);
if (attr.src) {
- attr.onClick = this.handlePreviewImage;
+ attr.onClick = this.handleImagePreview;
attr.className += ' image-clickable';
} else {
attr.src = 'img/broken_image.png';
diff --git a/src/widgets/image-preview.jsx b/src/widgets/image-preview.jsx
index 6b72c152..3f8a4834 100644
--- a/src/widgets/image-preview.jsx
+++ b/src/widgets/image-preview.jsx
@@ -1,5 +1,6 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
+import SendMessage from '../widgets/send-message.jsx';
import { REM_SIZE } from '../config.js';
import { fitImageSize } from '../lib/blob-helpers.js';
@@ -60,23 +61,30 @@ export default class ImagePreview extends React.PureComponent {
this.assignWidth(node)}>
-
+ {this.props.onSendMessage ?
+
+ :
+ }
);
}
diff --git a/src/widgets/send-message.jsx b/src/widgets/send-message.jsx
index 11e39774..8985e36d 100644
--- a/src/widgets/send-message.jsx
+++ b/src/widgets/send-message.jsx
@@ -3,8 +3,8 @@ import React from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { Drafty } from 'tinode-sdk';
-import { KEYPRESS_DELAY, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';
-import { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64, imageFileScaledToBase64 } from '../lib/blob-helpers.js';
+import { KEYPRESS_DELAY, MAX_EXTERN_ATTACHMENT_SIZE, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';
+import { filePasted, fileToBase64 } from '../lib/blob-helpers.js';
import { bytesToHumanSize } from '../lib/strformat.js';
const messages = defineMessages({
@@ -81,32 +81,7 @@ class SendMessage extends React.PureComponent {
handleAttachImage(e) {
if (e.target.files && e.target.files.length > 0) {
- const file = e.target.files[0];
- // Check if the uploaded file is indeed an image and if it isn't too large.
- if (file.size > MAX_INBAND_ATTACHMENT_SIZE || SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) {
- // Convert image for size or format.
- imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false,
- // Success
- (bits, mime, width, height, fname) => {
- this.props.onAttachImage(mime, bits, width, height, fname);
- },
- // Failure
- (err) => {
- this.props.onError(err, 'err');
- });
- } else {
- // Image can be uploaded as is. No conversion is needed.
- imageFileToBase64(file,
- // Success
- (bits, mime, width, height, fname) => {
- this.props.onAttachImage(mime, bits, width, height, fname);
- },
- // Failure
- (err) => {
- this.props.onError(err, 'err');
- }
- );
- }
+ this.props.onAttachImage(e.target.files[0]);
}
// Clear the value so the same file can be uploaded again.
e.target.value = '';
@@ -186,16 +161,23 @@ class SendMessage extends React.PureComponent {
formatMessage(messages.type_new_message);
return (
\n :\n null;\n }\n};\n","// This is a generated file. Don't edit.\n\nexport const PACKAGE_VERSION = \"0.16.3-rc5\";\n","import { PACKAGE_VERSION } from './version.js';\n\n// Name of this application, used in the User-Agent.\nexport const APP_NAME = 'TinodeWeb/' + (PACKAGE_VERSION || '0.16');\n\n// API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own\nexport const API_KEY = 'AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K';\n\n// The array of possible hosts to connect to.\nexport const KNOWN_HOSTS = {hosted: 'web.tinode.co', local: 'localhost:6060'};\n\n// Default host name and port to connect to.\nexport const DEFAULT_HOST = KNOWN_HOSTS.hosted;\n\n// Enable console logging.\nexport const LOGGING_ENABLED = true;\n\n// Minimum time between two keypress notifications, milliseconds.\nexport const KEYPRESS_DELAY = 3*1000;\n// Delay before sending a {note} for reciving a message, milliseconds.\nexport const RECEIVED_DELAY = 500;\n// Delay before sending a read notification, milliseconds.\nexport const READ_DELAY = 1000;\n\n// The shortest allowed tag length. Matches the value on the server.\nexport const MIN_TAG_LENGTH = 4;\n\n// Access mode for P2P subscriptions initiated by the current user.\nexport const DEFAULT_P2P_ACCESS_MODE = 'JRWPS';\n// Access mode for new group topics created by the current user.\nexport const NEW_GRP_ACCESS_MODE = 'JRWPSAO';\n\n// Access mode for no access.\nexport const NO_ACCESS_MODE = 'N';\n\n// Mediaquery breakpoint between desktop and mobile, in px. Should match the value\n// in @media (max-size: 640px) in base.css\nexport const MEDIA_BREAKPOINT = 640;\n// Size of css 'rem' unit in pixels. Default 1rem = 10pt = 13px.\nexport const REM_SIZE = 13;\n\n// Size of the avatar image: When an avatar image is uploaded, it's resized to\n// a square of this size.\nexport const AVATAR_SIZE = 128;\n\n// Size of the broken_image shown in MessagesView\nexport const BROKEN_IMAGE_SIZE = 32;\n\n// Number of chat messages to fetch in one call.\nexport const MESSAGES_PAGE = 24;\n\n// Maximum in-band (included directly into the message) attachment size which fits into\n// a message of 256K in size, assuming base64 encoding and 1024 bytes of overhead.\n// This is size of an object *before* base64 encoding is applied.\n// Increase this limit to a greater value in production, if desired. Also increase\n// max_message_size in server config.\n// MAX_INBAND_ATTACHMENT_SIZE = base64DecodedLen(max_message_size - overhead);\nexport const MAX_INBAND_ATTACHMENT_SIZE = 195840;\n\n// Absolute maximum attachment size to be used with the server = 8MB. Increase to\n// something like 100MB in production.\nexport const MAX_EXTERN_ATTACHMENT_SIZE = 1 << 23;\n\n// Maximum allowed linear dimension of an inline image in pixels. You may want\n// to adjust it to 1600 or 2400 for production.\nexport const MAX_IMAGE_DIM = 768;\n\n// Maximum number of online users to be shown in a topic title bar. Others will be\n// hidden under \"+X more\"\nexport const MAX_ONLINE_IN_TOPIC = 4;\n\n// Maximum length of user name, topic title, and private comment.\nexport const MAX_TITLE_LENGTH = 60;\n\n// Link for \"Contact Us\".\nexport const LINK_CONTACT_US = 'email:info@tinode.co';\n\n// Link to Privacy Policy.\nexport const LINK_PRIVACY_POLICY = 'https://tinode.co/privacy.html';\n\n// Link to Terms of Service.\nexport const LINK_TERMS_OF_SERVICE = 'https://tinode.co/terms.html';\n","// Utility class for hash navigation.\n\n// Parse hash as in http://www.example.com/path#hash as if it were\n// path and arguments.\nexport default class HashNavigation {\n static parseUrlHash(hash) {\n // Split path from args, path -> parts[0], args->path[1]\n let parts = hash.split('?', 2);\n let params = {};\n let path = [];\n if (parts[0]) {\n path = parts[0].substr(1).split('/');\n }\n if (parts[1]) {\n parts[1].split('&').forEach(function(part) {\n let item = part.split('=');\n if (item[0]) {\n params[decodeURIComponent(item[0])] = decodeURIComponent(item[1]);\n }\n });\n }\n return {path: path, params: params};\n }\n\n static navigateTo(url) {\n window.location.hash = url;\n }\n\n static composeUrlHash(path, params) {\n var url = path.join('/');\n var args = [];\n for (var key in params) {\n if (params.hasOwnProperty(key)) {\n args.push(key + '=' + params[key]);\n }\n }\n if (args.length > 0) {\n url += '?' + args.join('&');\n }\n return url;\n }\n\n static addUrlParam(hash, key, value) {\n var parsed = this.parseUrlHash(hash);\n parsed.params[key] = value;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static removeUrlParam(hash, key) {\n var parsed = this.parseUrlHash(hash);\n delete parsed.params[key];\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static setUrlSidePanel(hash, sidepanel) {\n var parsed = this.parseUrlHash(hash);\n parsed.path[0] = sidepanel;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static setUrlTopic(hash, topic) {\n var parsed = this.parseUrlHash(hash);\n parsed.path[1] = topic;\n // Close InfoView on topic change.\n delete parsed.params.info;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n}\n","// Context Menu: popup/dropdown menu.\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport { injectIntl, defineMessages } from 'react-intl';\n\nimport { REM_SIZE } from '../config.js';\nimport HashNavigation from '../lib/navigation.js';\n\nconst messages = defineMessages({\n info: {\n id: 'menu_item_info',\n defaultMessage: 'Info',\n description: 'Show extended topic information'\n },\n clear_messages: {\n id: 'menu_item_clear_messages',\n defaultMessage: 'Clear messages',\n description: 'Delete all messages'\n },\n clear_for_all: {\n id: 'menu_item_clear_messages_for_all',\n defaultMessage: 'Clear for All',\n description: 'Delete all message(s) for all members'\n },\n 'delete': {\n id: 'menu_item_delete',\n defaultMessage: 'Delete',\n description: 'Delete selected messages'\n },\n delete_for_all: {\n id: 'menu_item_delete_for_all',\n defaultMessage: 'Delete for All',\n description: 'Delete selected message(s) for all members'\n },\n mute: {\n id: 'menu_item_mute',\n defaultMessage: 'Mute',\n description: 'Turn off notifications'\n },\n unmute: {\n id: 'menu_item_unmute',\n defaultMessage: 'Unmute',\n description: 'Turn notifications on'\n },\n topic_delete: {\n id: 'menu_item_delete_topic',\n defaultMessage: 'Delete',\n description: 'Delete entire topic'\n },\n topic_delete_warning: {\n id: 'topic_delete_warning',\n defaultMessage: 'Are you sure you want to delete this conversation?',\n description: 'Alert warning when deleting entire topic'\n },\n unblock: {\n id: 'menu_item_unblock',\n defaultMessage: 'Unblock',\n description: 'Unblock topic or user'\n },\n // Generic string suitable for either topic or user.\n block: {\n id: 'menu_item_block',\n defaultMessage: 'Block',\n description: 'Block topic or user'\n },\n topic_block_warning: {\n id: 'topic_block_warning',\n defaultMessage: 'Are you sure you want to block this conversation?',\n description: 'Alert warning when blocking a topic.'\n },\n member_delete: {\n id: 'menu_item_member_delete',\n defaultMessage: 'Remove',\n description: 'Remove user from topic'\n },\n archive: {\n id: 'menu_item_archive_topic',\n defaultMessage: 'Archive',\n description: 'Move topic from the list of active chats to archive'\n }\n});\n\nclass ContextMenu extends React.Component {\n constructor(props) {\n super(props);\n\n const {formatMessage} = props.intl;\n\n this.handlePageClick = this.handlePageClick.bind(this);\n this.handleEscapeKey = this.handleEscapeKey.bind(this);\n this.handleClick = this.handleClick.bind(this);\n\n // Preconfigured menu items.\n this.MenuItems = {\n 'topic_info': {\n id: 'topic_info',\n title: formatMessage(messages.info),\n handler: null\n },\n 'messages_clear': {\n id: 'messages_clear',\n title: formatMessage(messages.clear_messages),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage({id: 'menu_item_clear_messages'}), // title\n formatMessage({id: 'clear_messages_warning'}), // content\n (() => { this.deleteMessages(true, false, params, errorHandler); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'messages_clear_hard': {\n id: 'messages_clear_hard',\n title: formatMessage(messages.clear_for_all),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage({id: 'menu_item_clear_messages_for_all'}), // title\n formatMessage({id: 'delete_messages_warning'}), // content\n (() => { return this.deleteMessages(true, true, params, errorHandler); }),\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'message_delete': {\n id: 'message_delete',\n title: formatMessage(messages.delete),\n handler: (params, errorHandler) => {\n return this.deleteMessages(false, false, params, errorHandler);\n }\n },\n 'message_delete_hard': {\n id: 'message_delete_hard',\n title: formatMessage(messages.delete_for_all),\n handler: (params, errorHandler) => {\n return this.deleteMessages(false, true, params, errorHandler);\n }\n },\n 'topic_unmute': {\n id: 'topic_unmute',\n title: formatMessage(messages.unmute),\n handler: this.topicPermissionSetter.bind(this, '+P')\n },\n 'topic_mute': {\n id: 'topic_mute',\n title: formatMessage(messages.mute),\n handler: this.topicPermissionSetter.bind(this, '-P')\n },\n 'topic_unblock': {\n id: 'topic_unblock',\n title: formatMessage(messages.unblock),\n handler: this.topicPermissionSetter.bind(this, '+JP')\n },\n 'topic_block': {\n id: 'topic_block',\n title: formatMessage(messages.block),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage({id: 'menu_item_block'}), // title\n formatMessage(messages.topic_block_warning), // content\n (() => {\n return this.topicPermissionSetter('-JP', params, errorHandler).then((ctrl) => {\n this.props.onTopicRemoved(params.topicName);\n return ctrl;\n });\n }),\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'topic_delete': {\n id: 'topic_delete',\n title: formatMessage(messages.topic_delete),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage({id: 'menu_item_delete_topic'}), // title\n formatMessage(messages.topic_delete_warning), // content\n (() => {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found: \", params.topicName);\n return;\n }\n return topic.delTopic().catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }),\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'topic_archive': {\n id: 'topic_archive',\n title: formatMessage(messages.archive),\n handler: (params, errorHandler) => {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found: \", params.topicName);\n return;\n }\n return topic.archive(true).catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }\n },\n // menu_item_edit_permissions is defined elsewhere.\n 'permissions': {\n id: 'permissions',\n title: formatMessage({id: 'menu_item_edit_permissions'}),\n handler: null\n },\n 'member_delete': {\n id: 'member_delete',\n title: formatMessage(messages.member_delete),\n handler: (params, errorHandler) => {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic || !params.user) {\n console.log(\"Topic or user not found: '\" + params.topicName + \"', '\" + params.user + \"'\");\n return;\n }\n return topic.delSubscription(params.user).catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }\n },\n 'member_mute': {\n id: 'member_mute',\n title: formatMessage(messages.mute),\n handler: this.topicPermissionSetter.bind(this, '-P')\n },\n 'member_unmute': {\n id: 'member_unmute',\n title: formatMessage(messages.unmute),\n handler: this.topicPermissionSetter.bind(this, '+P')\n },\n 'member_block': {\n id: 'member_block',\n title: formatMessage(messages.block),\n handler: this.topicPermissionSetter.bind(this, '-J')\n },\n 'member_unblock': {\n id: 'member_unblock',\n title: formatMessage(messages.unblock),\n handler: this.topicPermissionSetter.bind(this, '+J')\n },\n };\n }\n\n componentDidMount() {\n document.addEventListener('mousedown', this.handlePageClick, false);\n document.addEventListener('keyup', this.handleEscapeKey, false);\n }\n\n componentWillUnmount() {\n document.removeEventListener('mousedown', this.handlePageClick, false);\n document.removeEventListener('keyup', this.handleEscapeKey, false);\n }\n\n handlePageClick(e) {\n if (ReactDOM.findDOMNode(this).contains(e.target)) {\n return;\n }\n e.preventDefault();\n e.stopPropagation();\n this.props.hide();\n }\n\n handleEscapeKey(e) {\n if (e.keyCode === 27) {\n this.props.hide();\n }\n }\n\n handleClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.hide();\n let item = this.props.items[e.currentTarget.dataset.id];\n if (typeof item == 'string') {\n item = this.MenuItems[item];\n }\n\n if (!item) {\n console.log(\"Invalid menu item ID\", e.currentTarget.dataset.id);\n } else {\n this.props.onAction(\n item.id,\n item.handler(this.props.params, this.props.onError),\n this.props.params);\n }\n }\n\n // Menu Actions\n\n deleteMessages(all, hard, params, errorHandler) {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found: \", params.topicName);\n return;\n }\n\n // We don't know if the message is still pending (e.g. attachment is being uploaded),\n // so try cancelling first. No harm if we can't cancel.\n // The message can be cancelled if transmission to the server has not\n // started yet or if the message send has failed.\n if (!all && topic.cancelSend(params.seq)) {\n return;\n }\n // Can't cancel. Delete instead.\n const promise = all ?\n topic.delMessagesAll(hard) :\n topic.delMessagesList([params.seq], hard);\n\n return promise.catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }\n\n // Function is used by context menu to set permissions.\n topicPermissionSetter(mode, params, errorHandler) {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found\", params.topicName);\n return;\n }\n\n let result = topic.updateMode(params.user, mode);\n if (errorHandler) {\n result = result.catch((err) => {\n errorHandler(err.message, 'err');\n });\n }\n return result;\n }\n\n render() {\n let count = 0;\n let menu = [];\n this.props.items.map((item) => {\n if (typeof item == 'string') {\n item = this.MenuItems[item];\n }\n if (item && item.title) {\n menu.push(\n item.title == '-' ?\n \n :\n {item.title} \n );\n }\n count++;\n });\n\n // Ensure that menu is inside the app-container.\n const hSize = 12 * REM_SIZE;\n const vSize = REM_SIZE * (0.7 + menu.length * 2.5);\n const left = (this.props.bounds.right - this.props.clickAt.x < hSize) ?\n (this.props.clickAt.x - this.props.bounds.left - hSize) :\n (this.props.clickAt.x - this.props.bounds.left);\n const top = (this.props.bounds.bottom - this.props.clickAt.y < vSize) ?\n (this.props.clickAt.y - this.props.bounds.top - vSize) :\n (this.props.clickAt.y - this.props.bounds.top);\n\n const position = {\n left: left + 'px',\n top: top + 'px'\n };\n\n return (\n \n );\n }\n}\n\nexport default injectIntl(ContextMenu);\n","// Short representation of time in the past.\nexport function shortDateFormat(then, locale) {\n locale = locale || window.navigator.userLanguage || window.navigator.language;\n const now = new Date();\n if (then.getFullYear() == now.getFullYear()) {\n if (then.getMonth() == now.getMonth() && then.getDate() == now.getDate()) {\n\t return then.toLocaleTimeString(locale, {hour12: false, hour: '2-digit', minute: '2-digit'});\n } else {\n\t return then.toLocaleDateString(locale,\n {hour12: false, month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'});\n }\n }\n return then.toLocaleDateString(locale,\n {hour12: false, year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'});\n}\n\n// Convert seconds to minutes:seconds, i.e. 156 sec -> 2:36.\nexport function secondsToTime(seconds) {\n const min = Math.floor(seconds / 60);\n let sec = seconds % 60;\n sec = sec < 10 ? `0${sec}` : sec;\n return `${min}:${sec}`;\n}\n\n// Convert a number of bytes to human-readable format.\nexport function bytesToHumanSize(bytes) {\n if (!bytes || bytes == 0) {\n return '0 Bytes';\n }\n\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];\n const bucket = Math.min(Math.floor(Math.log2(bytes) / 10) | 0, sizes.length-1);\n const count = bytes / Math.pow(1024, bucket);\n const round = bucket > 0 ? (count < 3 ? 2 : (count < 30 ? 1 : 0)) : 0;\n return count.toFixed(round) + ' ' + sizes[bucket];\n}\n\n// Get 32 bit integer hash value for a string. Ideally it should produce the same value\n// as Java's String#hash().\nexport function stringHash(value) {\n let hash = 0;\n value = '' + value;\n for (let i = 0; i < value.length; i++) {\n hash = ((hash << 5) - hash) + value.charCodeAt(i);\n hash = hash & hash; // Convert to 32bit integer\n }\n return hash;\n}\n","import React from 'react';\n\nimport Tinode from 'tinode-sdk';\n\nimport { stringHash } from '../lib/strformat.js';\n\nexport default class LetterTile extends React.PureComponent {\n render() {\n let avatar;\n if (this.props.avatar === true) {\n const isGroup = (Tinode.topicType(this.props.topic) == 'grp');\n const iconColor = (isGroup ? 'light-color' : 'dark-color')\n + (Math.abs(stringHash(this.props.topic)) % 16);\n if (this.props.topic && this.props.title && this.props.title.trim()) {\n const letter = this.props.title.trim().charAt(0);\n const className = 'lettertile ' + iconColor;\n avatar = ()\n } else {\n const className = 'material-icons ' + iconColor;\n avatar = isGroup ?\n group : person ;\n }\n } else if (this.props.avatar) {\n // If avatar image is invalid, show a placeholder.\n avatar = {e.target.onerror = null; e.target.src=\"/img/broken_image.png\"}} />;\n } else {\n avatar = null;\n }\n return avatar;\n }\n}\n","// File and image helper functions.\nimport { MAX_INBAND_ATTACHMENT_SIZE, MAX_IMAGE_DIM } from '../config.js';\n\n// Supported image MIME types and corresponding file extensions.\nexport const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];\nexport const MIME_EXTENSIONS = ['jpg', 'gif', 'png', 'svg', 'svg'];\n\n// Make a data URL from public.photo\nexport function makeImageUrl(photo) {\n return (photo && photo.type && photo.data) ?\n 'data:image/' + photo.type + ';base64,' + photo.data : null;\n}\n\n// Calculate linear dimensions for scaling image down to fit under a certain size.\n// Returns an object which contains destination sizes, source sizes, and offsets\n// into source (when making square images).\nexport function fitImageSize(width, height, maxWidth, maxHeight, forceSquare) {\n // Sanitize input\n width = width | 0;\n height = height | 0;\n maxWidth = maxWidth | 0;\n maxHeight = maxHeight | 0;\n\n if (width <= 0 || height <= 0 || maxWidth <= 0 || maxHeight <= 0) {\n return null;\n }\n\n if (forceSquare) {\n maxWidth = maxHeight = Math.min(maxWidth, maxHeight);\n }\n\n let scale = Math.min(\n Math.min(width, maxWidth) / width,\n Math.min(height, maxHeight) / height\n );\n\n let size = {\n dstWidth: (width * scale) | 0,\n dstHeight: (height * scale) | 0,\n };\n\n if (forceSquare) {\n // Also calculate parameters for making the image square.\n size.dstWidth = size.dstHeight = Math.min(size.dstWidth, size.dstHeight);\n size.srcWidth = size.srcHeight = Math.min(width, height);\n size.xoffset = ((width - size.srcWidth) / 2) | 0;\n size.yoffset = ((height - size.srcWidth) / 2) | 0;\n } else {\n size.xoffset = size.yoffset = 0;\n size.srcWidth = width;\n size.srcHeight = height;\n }\n return size;\n}\n\n// Ensure file's extension matches mime content type\nexport function fileNameForMime(fname, mime) {\n var idx = SUPPORTED_IMAGE_FORMATS.indexOf(mime);\n var ext = MIME_EXTENSIONS[idx];\n\n var at = fname.lastIndexOf('.');\n if (at >= 0) {\n fname = fname.substring(0, at);\n }\n return fname + '.' + ext;\n}\n\n// Convert uploaded image into a base64-encoded string possibly scaling\n// linear dimensions or constraining to a square.\nexport function imageFileScaledToBase64(file, width, height, forceSquare, onSuccess, onError) {\n var img = new Image();\n img.crossOrigin = 'Anonymous';\n img.onerror = function(err) {\n onError(\"Image format unrecognized\");\n }\n img.onload = function() {\n var dim = fitImageSize(this.width, this.height, width, height, forceSquare);\n if (!dim) {\n onError(\"Invalid image\");\n return;\n }\n var canvas = document.createElement('canvas');\n canvas.width = dim.dstWidth;\n canvas.height = dim.dstHeight;\n var ctx = canvas.getContext('2d');\n ctx.imageSmoothingEnabled = true;\n ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight,\n 0, 0, dim.dstWidth, dim.dstHeight);\n\n var mime = (this.width != dim.dstWidth ||\n this.height != dim.dstHeight ||\n SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) ? 'image/jpeg' : file.type;\n var imageBits = canvas.toDataURL(mime);\n var parts = imageBits.split(',');\n // Get actual image type: 'data:image/png;base64,'\n mime = getMimeType(parts[0]);\n if (!mime) {\n onError(\"Unsupported image format\");\n return;\n }\n // Ensure the image is not too large\n var quality = 0.78;\n if (base64DecodedLen(imageBits.length) > MAX_INBAND_ATTACHMENT_SIZE) {\n mime = 'image/jpeg';\n }\n if (mime == 'image/jpeg') {\n // Reduce size of the jpeg by reducing image quality\n while (base64DecodedLen(imageBits.length) > MAX_INBAND_ATTACHMENT_SIZE && quality > 0.45) {\n imageBits = canvas.toDataURL(mime, quality);\n quality *= 0.84;\n }\n }\n if (base64DecodedLen(imageBits.length) > MAX_INBAND_ATTACHMENT_SIZE) {\n onError(\"The image size \" + bytesToHumanSize(base64DecodedLen(imageBits.length)) +\n \" exceeds the \" + bytesToHumanSize(MAX_INBAND_ATTACHMENT_SIZE) + \" limit.\", \"err\");\n return;\n }\n canvas = null;\n onSuccess(imageBits.split(',')[1], mime, dim.dstWidth, dim.dstHeight, fileNameForMime(file.name, mime));\n };\n img.src = URL.createObjectURL(file);\n}\n\n// Convert uploaded image file to base64-encoded string without scaling/converting the image\nexport function imageFileToBase64(file, onSuccess, onError) {\n var reader = new FileReader();\n reader.addEventListener('load', function() {\n var parts = reader.result.split(',');\n var mime = getMimeType(parts[0]);\n if (!mime) {\n onError(\"Failed to process image file\");\n return;\n }\n\n // Get image size.\n var img = new Image();\n img.crossOrigin = 'Anonymous';\n img.onload = function() {\n onSuccess(parts[1], mime, this.width, this.height, fileNameForMime(file.name, mime));\n }\n img.onerror = function(err) {\n onError(\"Image format not recognized\");\n }\n img.src = URL.createObjectURL(file);\n }, false);\n reader.readAsDataURL(file);\n}\n\nexport function fileToBase64(file, onSuccess, onError) {\n var reader = new FileReader();\n reader.addEventListener('load', function() {\n onSuccess(file.type, reader.result.split(',')[1], file.name);\n });\n reader.readAsDataURL(file);\n}\n\n// File pasted from the clipboard. It's either an inline image or a file attachment.\n// FIXME: handle large files out of band.\nexport function filePasted(event, onImageSuccess, onAttachmentSuccess, onError) {\n var items = (event.clipboardData || event.originalEvent.clipboardData || {}).items;\n for (var i in items) {\n var item = items[i];\n if (item.kind === 'file') {\n var file = item.getAsFile();\n if (!file) {\n console.log(\"Failed to get file object from pasted file item\", item.kind, item.type);\n continue;\n }\n if (file.type && file.type.split('/')[0] == 'image') {\n // Handle inline image\n if (file.size > MAX_INBAND_ATTACHMENT_SIZE || SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) {\n imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false, onImageSuccess, onError);\n } else {\n imageFileToBase64(file, onImageSuccess, onError);\n }\n } else {\n // Handle file attachment\n fileToBase64(file, onAttachmentSuccess, onError)\n }\n // Indicate that the pasted data contains a file.\n return true;\n }\n }\n // No file found.\n return false;\n}\n\n// Get mime type from data URL header.\nexport function getMimeType(header) {\n var mime = /^data:(image\\/[-+a-z0-9.]+);base64/.exec(header);\n return (mime && mime.length > 1) ? mime[1] : null;\n}\n\n// Given length of a binary object in bytes, calculate the length after\n// base64 encoding.\nexport function base64EncodedLen(n) {\n return Math.floor((n + 2) / 3) * 4;\n}\n\n// Given length of a base64-encoded object, calculate decoded size of the\n// pbject in bytes.\nexport function base64DecodedLen(n) {\n return Math.floor(n / 4) * 3;\n}\n\n// Re-encode string to standard base64 encoding with padding.\n// The string may be base64-URL encoded without the padding.\nexport function base64ReEncode(str) {\n if (str) {\n str = str.replace(/-/g, '+').replace(/_/g, '/');\n try {\n str = btoa(atob(str));\n } catch(err) {\n console.log(\"Failed to base64 re-encode string.\", err);\n str = null;\n }\n }\n return str;\n}\n","import React from 'react';\n\nimport LetterTile from './letter-tile.jsx';\n\nimport { AVATAR_SIZE } from '../config.js';\nimport { imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js';\n\nexport default class AvatarUpload extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n dataUrl: props.avatar\n };\n\n this.handleFileUpload = this.handleFileUpload.bind(this);\n }\n\n componentDidUpdate(prevProps) {\n if (this.props.avatar != prevProps.avatar) {\n this.setState({dataUrl: this.props.avatar})\n }\n }\n\n handleFileUpload(e) {\n imageFileScaledToBase64(e.target.files[0], AVATAR_SIZE, AVATAR_SIZE, true,\n // Success\n (base64bits, mime) => {\n var du = makeImageUrl({data: base64bits, type: mime});\n this.setState({dataUrl: du});\n this.props.onImageChanged(du);\n },\n // Failure\n (err) => {\n this.props.onError(err, 'err');\n });\n // Clear the value so the same file can be uploaded again.\n e.target.value = '';\n }\n\n render() {\n // Randomize id value in case more than one AvatarUpload is shown\n // at the same time.\n var randId = \"file-input-avatar-\" + (Math.random() + '').substr(2);\n return (\n \n {this.props.readOnly || !this.state.dataUrl ?\n null :\n
{e.preventDefault(); this.props.onImageChanged(null);}}>\n clear \n }\n {this.state.dataUrl ?\n
:\n this.props.readOnly && this.props.uid ?\n
\n \n
\n :\n
{AVATAR_SIZE}×{AVATAR_SIZE}
}\n {this.props.readOnly ? null :\n
}\n {this.props.readOnly ? null :\n
\n file_upload \n }\n
\n );\n }\n};\n","import React from 'react';\n\n/* BEGIN CheckBox: styled checkbox */\nexport default class CheckBox extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleChange = this.handleChange.bind(this);\n }\n\n handleChange() {\n this.props.onChange(this.props.name, !this.props.checked);\n }\n\n render() {\n return (\n this.props.onChange ? (\n this.props.checked ?\n check_box :\n check_box_outline_blank \n ) : (\n this.props.checked ?\n check_box :\n check_box_outline_blank \n )\n );\n }\n}\n/* END CheckBox */\n","import React from 'react';\n\nconst icon_mapping = {'muted': 'notifications_off', 'banned': 'block'};\n\n/* Contact's labels: [you], or icons [muted], [blocked] */\nexport default class ContactBadges extends React.PureComponent {\n render() {\n let badges = null;\n if (this.props.badges && this.props.badges.length > 0) {\n badges = [];\n this.props.badges.map(function(b) {\n if (b.icon) {\n badges.push({icon_mapping[b.icon]} );\n } else {\n const style = 'badge' + (b.color ? ' ' + b.color : '');\n badges.push({b.name} );\n }\n });\n return <>{badges}>;\n }\n return null;\n }\n};\n","// The counter of unread messages in the topic.\nimport React from 'react';\n\nexport default class UnreadBadge extends React.PureComponent {\n render() {\n return (this.props.count > 0 ?\n {this.props.count > 9 ? \"9+\" : this.props.count} \n : null);\n }\n};\n","// A single topic or user.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport LetterTile from './letter-tile.jsx';\nimport ContactBadges from './contact-badges.jsx';\nimport UnreadBadge from './unread-badge.jsx';\n\nexport default class Contact extends React.Component {\n constructor(props) {\n super(props);\n\n this.handleClick = this.handleClick.bind(this);\n this.handleContextClick = this.handleContextClick.bind(this);\n }\n\n handleClick(e) {\n e.preventDefault();\n e.stopPropagation();\n if (this.props.onSelected) {\n this.props.onSelected(this.props.item, this.props.index, this.props.now, this.props.acs);\n }\n }\n\n handleContextClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.showContextMenu({ topicName: this.props.item, y: e.pageY, x: e.pageX });\n }\n\n render() {\n let title = this.props.title;\n if (!title) {\n title = ;\n } else if (title.length > 30) {\n // FIXME: this is probably wrong for RTL languages.\n title = title.substring(0, 28) + '...';\n }\n const online = this.props.now ? 'online' : 'offline';\n const avatar = this.props.avatar ? this.props.avatar : true;\n const badges = this.props.badges ? this.props.badges.slice() : [];\n const icon_badges = [];\n if (this.props.acs) {\n if (this.props.showMode) {\n badges.push({name: this.props.acs.getMode(), key: 'mode'});\n }\n if (this.props.acs.isMuted()) {\n icon_badges.push({icon: 'muted'});\n }\n if (!this.props.acs.isJoiner()) {\n icon_badges.push({icon: 'banned'});\n }\n }\n\n return (\n \n \n \n {this.props.showOnline ? :\n (this.props.showCheckmark && this.props.selected ?\n check_circle \n : null)}\n
\n \n
{title} \n \n
\n {this.props.comment ?
{this.props.comment}
: null}\n
\n
\n {this.props.showContextMenu ?\n \n \n expand_more \n \n : null}\n \n );\n }\n};\n","// A panel included into a list of contacts with an action text.\nimport React from 'react';\nimport { injectIntl } from 'react-intl';\n\nclass ContactAction extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleClick = this.handleClick.bind(this);\n }\n\n handleClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.onAction(this.props.action);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n return (\n \n {formatMessage(this.props.title, this.props.values)}
\n \n );\n }\n};\n\nexport default injectIntl(ContactAction);\n","// ContactList: component for showing a list of contacts,\n// such as a list of group members in a group chat.\nimport React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport Contact from './contact.jsx';\nimport ContactAction from './contact-action.jsx';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\nconst messages = defineMessages({\n badge_you: {\n id: 'badge_you',\n defaultMessage: 'you',\n description: 'Badge for indicating the current user'\n },\n badge_owner: {\n id: 'badge_owner',\n defaultMessage: 'owner',\n description: 'Badge for indicating the owner'\n }\n});\n\nclass ContactList extends React.Component {\n render() {\n const {formatMessage} = this.props.intl;\n const showCheckmark = Array.isArray(this.props.topicSelected);\n const contactNodes = [];\n let contactsCount = 0;\n if (this.props.contacts && this.props.contacts.length > 0) {\n this.props.contacts.map((c) => {\n if (c.action) {\n // Action item\n contactNodes.push(\n );\n } else {\n // Normal contact\n const key = c.topic ? c.topic : c.user;\n // If filter function is provided, filter out the items\n // which don't satisfy the condition.\n if (this.props.filterFunc && this.props.filter) {\n let content = [key];\n if (c.private && c.private.comment) {\n content.push(('' + c.private.comment).toLowerCase());\n }\n if (c.public && c.public.fn) {\n content.push(('' + c.public.fn).toLowerCase());\n }\n if (!this.props.filterFunc(this.props.filter, content)) {\n return;\n }\n }\n\n const selected = showCheckmark ?\n (this.props.topicSelected.indexOf(key) > -1) :\n (this.props.topicSelected === key);\n const badges = [];\n if (this.props.showMode) {\n if (key === this.props.myUserId) {\n badges.push({name: formatMessage(messages.badge_you), color: 'green'});\n }\n if (c.acs && c.acs.isOwner()) {\n badges.push({name: formatMessage(messages.badge_owner), color: 'blue'});\n }\n }\n const comment = Array.isArray(c.private) ?\n c.private.join(',') : (c.private ? c.private.comment : null);\n\n contactNodes.push(\n \n );\n contactsCount ++;\n }\n }, this);\n }\n\n return (\n \n {contactsCount == 0 ?\n
\n :\n null\n }\n {contactNodes.length > 0 ?\n
\n :\n null\n }\n
\n );\n }\n};\n\nexport default injectIntl(ContactList);\n","// The X menu to be displayed in title bars.\nimport React from 'react';\n\nexport default class MenuCancel extends React.PureComponent {\n render() {\n return (\n {e.preventDefault(); this.props.onCancel();}}>close \n );\n }\n}\n","import React from 'react';\n\nimport MenuCancel from './menu-cancel.jsx';\n\nexport default class ErrorPanel extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n show: false,\n };\n\n this.hide = this.hide.bind(this);\n }\n\n componentDidUpdate(prevProps) {\n if (prevProps.level !== this.props.level) {\n this.setState({\n show: !(!this.props.level)\n });\n }\n }\n\n hide() {\n this.setState({show: false});\n if (this.props.onClearError) {\n this.props.onClearError();\n }\n }\n\n render() {\n const icons = {err: 'error', warn: 'warning', info: 'info'}\n const level = icons[this.props.level] || '';\n const className = 'info-box ' + level;\n return (\n \n );\n }\n};\n","import React from 'react';\n\nimport LetterTile from './letter-tile.jsx';\n\nexport default class Chip extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n handleCancel(e) {\n e.preventDefault();\n this.props.onCancel(this.props.topic, this.props.index);\n }\n\n render() {\n const title = this.props.title || this.props.topic;\n const className = this.props.invalid ? 'chip invalid' : 'chip';\n return (\n \n {this.props.noAvatar ?\n
:\n
\n \n
\n }\n
{title} \n {this.props.onCancel && !this.props.required ?\n
× \n :
}\n
\n );\n }\n};\n","import React from 'react';\n\nimport Chip from './chip.jsx';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\n/* BEGIN ChipInput: group membership widget */\nexport default class ChipInput extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = ChipInput.deriveStateFromProps(props);\n this.state.input = '';\n this.state.focused = false;\n\n this.handleTextInput = this.handleTextInput.bind(this);\n this.removeChipAt = this.removeChipAt.bind(this);\n this.handleChipCancel = this.handleChipCancel.bind(this);\n this.handleFocusGained = this.handleFocusGained.bind(this);\n this.handleFocusLost = this.handleFocusLost.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n }\n\n static deriveStateFromProps(props) {\n return {\n placeholder: props.chips ? '' : props.prompt,\n sortedChips: ChipInput.sortChips(props.chips, props.staticMembers),\n chipIndex: ChipInput.indexChips(props.chips)\n };\n }\n\n componentDidUpdate(prevProps, prevState) {\n if (prevProps.chips != this.props.chips ||\n prevProps.staticMembers != this.props.staticMembers ||\n prevProps.prompt != this.props.prompt) {\n this.setState(ChipInput.deriveStateFromProps(this.props));\n }\n if (!prevState || this.props.chips.length > prevState.sortedChips.length) {\n this.setState({input: ''});\n }\n }\n\n // Map chip index to user name\n static indexChips(chips) {\n const index = {};\n let count = 0;\n chips.map((item) => {\n index[item.user] = count;\n count ++;\n });\n return index;\n }\n\n // Have non-removable chips appear before all other chips.\n static sortChips(chips, keep) {\n const required = [];\n const normal = [];\n chips.map((item) => {\n if (keep && keep.includes(item.user)) {\n required.push(item);\n } else {\n normal.push(item);\n }\n });\n return required.concat(normal);\n }\n\n handleTextInput(e) {\n this.setState({input: e.target.value});\n if (this.props.filterFunc) {\n this.props.filterFunc(e.target.value);\n }\n }\n\n removeChipAt(idx) {\n const removed = this.state.sortedChips[idx];\n this.props.onChipRemoved(removed.user, this.state.chipIndex[removed.user]);\n }\n\n handleChipCancel(item, idx) {\n this.removeChipAt(idx);\n }\n\n handleFocusGained() {\n this.setState({focused: true});\n }\n\n handleFocusLost() {\n this.setState({focused: false});\n if (this.props.onFocusLost) {\n this.props.onFocusLost(this.state.input);\n }\n }\n\n handleKeyDown(e) {\n if (e.key === 'Backspace') {\n if (this.state.input.length == 0 && this.state.sortedChips.length > 0) {\n const at = this.state.sortedChips.length - 1;\n if (this.state.sortedChips[at].user !== this.props.staticMembers) {\n this.removeChipAt(at);\n }\n }\n } else if (e.key === 'Enter') {\n if (this.props.onEnter) {\n this.props.onEnter(this.state.input);\n }\n } else if (e.key === 'Escape') {\n if (this.props.onCancel) {\n this.props.onCancel();\n }\n }\n }\n\n render() {\n const chips = [];\n let count = 0;\n const staticMembers = this.props.staticMembers || [];\n this.state.sortedChips.map((item) => {\n chips.push(\n \n );\n count++;\n });\n const className = \"chip-input\" + (this.state.focused ? ' focused' : '');\n return (\n \n {chips}\n \n
\n );\n }\n};\n","// GroupMembers: control for managing a list of group members.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport ChipInput from './chip-input.jsx';\nimport ContactList from './contact-list.jsx';\n\nconst messages = defineMessages({\n no_contacts: {\n id: 'no_contacts',\n defaultMessage: 'You have no contacts :-(',\n description: 'Shown in ContactsView when the user has no contacts'\n },\n contacts_not_found: {\n id: 'contacts_not_found_short',\n defaultMessage: \"No contacts match '{query}'\",\n description: 'Shown in ContactsView when search returned no results'\n }\n});\n\nclass GroupManager extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n // Array of topic members\n members: props.members,\n index: GroupManager.indexMembers(props.members),\n staticMembers: GroupManager.staticMembers(props.members, props.keepInitialMembers, props.requiredMember),\n contactFilter: '',\n noContactsMessage: props.intl.formatMessage(messages.no_contacts),\n selectedContacts: GroupManager.selectedContacts(props.members)\n };\n\n this.handleContactSelected = this.handleContactSelected.bind(this);\n this.handleMemberRemoved = this.handleMemberRemoved.bind(this);\n this.handleContactFilter = this.handleContactFilter.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n static indexMembers(members) {\n let index = {};\n members.map((m) => {\n index[m.user] = {delta: 0, present: true}; // Delta: 0 unchanged, +1 added, -1 removed\n });\n return index;\n }\n\n static staticMembers(members, keepInitial, requiredMember) {\n let stat = [];\n members.map((m) => {\n if (keepInitial || m.user == requiredMember) {\n stat.push(m.user);\n }\n });\n return stat;\n }\n\n static selectedContacts(members) {\n let sel = [];\n members.map((m) => {\n sel.push(m.user);\n });\n return sel;\n }\n\n handleContactSelected(userId, index) {\n let status = this.state.index[userId];\n if (status) {\n if (status.present) {\n // Prevent duplicate members\n return;\n }\n status.delta += 1;\n status.present = true;\n } else {\n status = {delta: 1, present: true};\n }\n\n let m = this.state.members.slice();\n m.push(this.props.contacts[index]);\n\n const sel = GroupManager.selectedContacts(m);\n\n const i = this.state.index;\n i[userId] = status;\n\n this.setState({members: m, index: i, selectedContacts: sel});\n }\n\n handleMemberRemoved(userId, index) {\n const status = this.state.index[userId];\n if (!status || !status.present) {\n return;\n }\n status.present = false;\n status.delta -= 1;\n\n let m = this.state.members.slice();\n m.splice(index, 1);\n\n const sel = GroupManager.selectedContacts(m);\n\n const i = this.state.index;\n i[userId] = status;\n\n this.setState({members: m, index: i, selectedContacts: sel});\n }\n\n handleContactFilter(val) {\n const {formatMessage} = this.props.intl;\n const msg = !val ?\n formatMessage(messages.no_contacts) :\n formatMessage(messages.contacts_not_found, {query: val});\n\n this.setState({contactFilter: val, noContactsMessage: msg});\n }\n\n static doContactFiltering(filter, values) {\n if (filter) {\n for (let i=0; i= 0) {\n return true;\n }\n }\n return false;\n }\n return true;\n }\n\n handleSubmit() {\n var instance = this;\n var members = [];\n var added = [];\n var removed = [];\n\n var keys = Object.keys(this.state.index);\n keys.map(function(k) {\n if (instance.state.index[k].present) {\n members.push(k);\n }\n\n if (instance.state.index[k].delta > 0) {\n added.push(k);\n } else if (instance.state.index[k].delta < 0) {\n removed.push(k);\n }\n });\n this.props.onSubmit(members, added, removed);\n }\n\n handleCancel() {\n this.props.onCancel();\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n return (\n \n
\n \n \n \n
\n
\n \n
\n
\n \n \n \n
\n
\n
\n \n \n \n \n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(GroupManager);\n","// Password with a visiblity toggle.\nimport React from 'react';\n\nexport default class VisiblePassword extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n value: this.props.value,\n visible: false\n };\n\n this.handleVisibility = this.handleVisibility.bind(this);\n this.handeTextChange = this.handeTextChange.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n }\n\n handeTextChange(e) {\n this.setState({value: e.target.value});\n if (this.props.onChange) {\n this.props.onChange(e);\n }\n }\n\n handleVisibility(e) {\n e.preventDefault();\n this.setState({visible: !this.state.visible});\n }\n\n handleKeyDown(e) {\n if (e.keyCode == 27) {\n // Escape pressed\n this.setState({value: this.props.value, visible: false});\n if (this.props.onFinished) {\n this.props.onFinished();\n }\n } else if (e.keyCode == 13) {\n // Enter pressed\n this.handleEditingFinished();\n }\n }\n\n handleEditingFinished(e) {\n if (e) {\n let currentTarget = e.currentTarget;\n setTimeout(() => {\n if (!currentTarget.contains(document.activeElement)) {\n if (this.props.onFinished) {\n this.props.onFinished(this.state.value);\n }\n }\n }, 0);\n } else if (this.props.onFinished) {\n this.props.onFinished(this.state.value.trim());\n }\n }\n\n render() {\n return (\n \n \n \n \n {this.state.visible ? 'visibility' : 'visibility_off'}\n \n \n
\n );\n }\n}\n","// In-place text editor. Shows text with an icon which toggles it into an input field.\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport VisiblePassword from './visible-password.jsx';\n\nexport default class InPlaceEdit extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n active: props.active,\n initialValue: props.value || '',\n value: props.value || ''\n };\n\n this.handeTextChange = this.handeTextChange.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n this.handleStartEditing = this.handleStartEditing.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n this.handlePasswordFinished = this.handlePasswordFinished.bind(this);\n }\n\n componentDidUpdate(prevProps, prevState) {\n // If text has changed while in read mode, update text and discard changes.\n // Ignore update if in edit mode.\n const newValue = this.props.value || '';\n if (prevState.initialValue != newValue && !prevState.active) {\n this.setState({\n initialValue: newValue,\n value: newValue\n });\n }\n }\n\n handeTextChange(e) {\n this.setState({value: e.target.value});\n }\n\n handleKeyDown(e) {\n if (e.keyCode === 27) {\n // Escape pressed\n this.setState({value: this.props.value, active: false});\n } else if (e.keyCode === 13) {\n // Enter pressed\n this.handleEditingFinished(e);\n }\n }\n\n handleStartEditing() {\n if (!this.props.readOnly) {\n ReactDOM.findDOMNode(this).focus();\n this.setState({active: true});\n }\n }\n\n handleEditingFinished(event) {\n if (this.props.required && !event.target.checkValidity()) {\n // Empty input\n this.setState({value: this.props.value, active: false});\n return;\n }\n this.setState({active: false});\n let value = this.state.value.trim();\n if ((value || this.props.value) && (value !== this.props.value)) {\n this.props.onFinished(value);\n }\n }\n\n handlePasswordFinished(value) {\n this.setState({active: false});\n if (value && (value !== this.props.value)) {\n this.props.onFinished(value);\n }\n }\n\n render() {\n if (this.state.active) {\n var fieldType = this.props.type || 'text';\n } else {\n var spanText = this.props.type == 'password' ? '••••••••' : this.state.value;\n var spanClass = 'in-place-edit' +\n (this.props.readOnly ? ' disabled' : '');\n if (!spanText) {\n spanText = this.props.placeholder;\n spanClass += ' placeholder';\n }\n if (spanText.length > 20) {\n // FIXME: this is wrong for RTL languages.\n spanText = spanText.substring(0, 19) + '...';\n }\n }\n return (\n this.state.active ?\n (fieldType == 'password' ?\n \n :\n \n )\n :\n \n {spanText} \n \n );\n }\n};\n","// Toggle [Title text >] -> [Title text v]\n\nimport React from 'react';\n\nexport default class MoreButton extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n open: props.open\n };\n this.handleToggle = this.handleToggle.bind(this);\n }\n\n handleToggle() {\n const open = !this.state.open;\n this.setState({open: open});\n if (this.props.onToggle) {\n this.props.onToggle(open);\n }\n }\n\n render() {\n return ({this.props.title}...\n {this.state.open ? expand_more :\n chevron_right }\n );\n }\n}\n","// PermissionsEditor: Component for editing permissions\n// \nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport CheckBox from './checkbox.jsx';\nimport Contact from './contact.jsx';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\n// Translatable permission names.\nconst messages = defineMessages({\n 'joiner': {\n id: 'permission_join',\n defaultMessage: \"Join ({val})\",\n description: 'Name of J permission'\n },\n 'reader': {\n id: 'permission_read',\n defaultMessage: \"Read ({val})\",\n description: 'Name of R permission'\n },\n 'writer': {\n id: 'permission_write',\n defaultMessage: \"Write ({val})\",\n description: 'Name of W permission'\n },\n 'preser': {\n id: 'permission_pres',\n defaultMessage: \"Get notified ({val})\",\n description: 'Name of P permission'\n },\n 'approver': {\n id: 'permission_admin',\n defaultMessage: \"Approve ({val})\",\n description: 'Name of A permission'\n },\n 'sharer': {\n id: 'permission_share',\n defaultMessage: \"Share ({val})\",\n description: 'Name of S permission'\n },\n 'deleter': {\n id: 'permission_delete',\n defaultMessage: \"Delete ({val})\",\n description: 'Name of D permission'\n },\n 'owner': {\n id: 'permission_owner',\n defaultMessage: \"Owner ({val})\",\n description: 'Name of O permission'\n }\n});\n\nclass PermissionsEditor extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n mode: (props.mode || '').replace('N', '')\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n handleChange(val) {\n let mode = this.state.mode;\n let idx = mode.indexOf(val);\n if (idx == -1) {\n mode += val;\n } else {\n mode = mode.replace(val, '');\n }\n this.setState({mode: mode});\n }\n\n handleSubmit() {\n // Normalize string, otherwise cannot check if mode has changed.\n var mode = (this.state.mode || 'N').split('').sort().join('');\n var before = (this.props.mode || 'N').split('').sort().join('')\n if (mode !== before) {\n this.props.onSubmit(mode);\n } else {\n this.props.onCancel();\n }\n }\n\n handleCancel() {\n this.props.onCancel();\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const all = 'JRWPASDO';\n const names = {\n 'J': formatMessage(messages.joiner, {val: 'J'}),\n 'R': formatMessage(messages.reader, {val: 'R'}),\n 'W': formatMessage(messages.writer, {val: 'W'}),\n 'P': formatMessage(messages.preser, {val: 'P'}),\n 'A': formatMessage(messages.approver, {val: 'A'}),\n 'S': formatMessage(messages.sharer, {val: 'S'}),\n 'D': formatMessage(messages.deleter, {val: 'D'}),\n 'O': formatMessage(messages.owner, {val: 'O'})\n };\n\n let skip = this.props.skip || '';\n let mode = this.state.mode;\n let compare = (this.props.compare || '').replace('N', '');\n let items = [];\n for (let i=0; i= 0 && mode.indexOf(c) < 0) {\n // Permission is marked as inactive: hide unchecked permissions, disable checked permissions\n continue;\n }\n items.push(\n \n {names[c]} \n {skip.indexOf(c) < 0 ?\n = 0)} onChange={this.handleChange}/>\n :\n = 0)} />\n } {this.props.compare ? \n = 0)}/>\n : null}\n \n );\n }\n\n return (\n \n {this.props.userTitle ?\n
: null}\n
\n
\n {this.props.compare ?\n \n {this.props.modeTitle} \n {this.props.compareTitle} \n :\n null}\n \n {items}\n
\n
\n
\n \n \n \n \n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(PermissionsEditor);\n","// Odds and ends\n\n// Make shortcut icon appear with a green dot + show unread count in title.\nexport function updateFavicon(count) {\n const oldIcon = document.getElementById('shortcut-icon');\n const head = document.head || document.getElementsByTagName('head')[0];\n const newIcon = document.createElement('link');\n newIcon.type = 'image/png';\n newIcon.id = 'shortcut-icon';\n newIcon.rel = 'shortcut icon';\n newIcon.href = 'img/logo32x32' + (count > 0 ? 'a' : '') + '.png';\n if (oldIcon) {\n head.removeChild(oldIcon);\n }\n head.appendChild(newIcon);\n\n document.title = (count > 0 ? '('+count+') ' : '') + 'Tinode';\n}\n\n// Create VCard which represents topic 'public' info\nexport function vcard(fn, imageDataUrl) {\n let card = null;\n\n if ((fn && fn.trim()) || imageDataUrl) {\n card = {};\n if (fn) {\n card.fn = fn.trim();\n }\n if (imageDataUrl) {\n const dataStart = imageDataUrl.indexOf(',');\n card.photo = dataStart >= 0 ? {\n data: imageDataUrl.substring(dataStart+1),\n type: 'jpg'\n } : imageDataUrl;\n }\n }\n return card;\n}\n\n// Deep-shallow compare two arrays: shallow compare each element.\nexport function arrayEqual(a, b) {\n if (a === b) {\n return true;\n }\n\n if (!Array.isArray(a) || !Array.isArray(b)) {\n return false;\n }\n\n // Compare lengths first.\n if (a.length != b.length) {\n return false;\n }\n // Order of elements is ignored.\n a.sort();\n b.sort();\n for (let i = 0, l = a.length; i < l; i++) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n}\n\n// Checks (loosely) if the given string is a phone. If so, returns the phone number in a format\n// as close to E.164 as possible.\nexport function asPhone(val) {\n val = val.trim();\n if (/^(?:\\+?(\\d{1,3}))?[- (.]*(\\d{3})[- ).]*(\\d{3})[- .]*(\\d{2})[- .]*(\\d{2})?$/.test(val)) {\n return val.replace(/[- ().]*/, '');\n }\n return null;\n}\n\n// Checks (loosely) if the given string is an email. If so returns the email.\nexport function asEmail(val) {\n val = val.trim();\n if (/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$/.test(val)) {\n return val;\n }\n return null;\n}\n\n// Checks if URL is a relative url, i.e. has no 'scheme://', including the case of missing scheme '//'.\n// The scheme is expected to be RFC-compliant, e.g. [a-z][a-z0-9+.-]*\n// example.html - ok\n// https:example.com - not ok.\n// http:/example.com - not ok.\n// ' ↲ https://example.com' - not ok. (↲ means carriage return)\nexport function isUrlRelative(url) {\n return !/^\\s*([a-z][a-z0-9+.-]*:|\\/\\/)/im.test(url);\n}\n\n// Ensure URL does not present an XSS risk. Optional allowedSchemes may contain an array of\n// strings with permitted URL schemes, such as ['ftp', 'ftps']; otherwise accept http and https only.\nexport function sanitizeUrl(url, allowedSchemes) {\n if (!url) {\n return null;\n }\n\n // Strip control characters and whitespace. They are not valid URL characters anyway.\n url = url.replace(/[^\\x20-\\x7E]/gmi, '').trim();\n\n // Relative URLs are safe.\n if (!/^([a-z][a-z0-9+.-]*:|\\/\\/)/i.test(url)) {\n return url;\n }\n // Blob URLs are safe.\n if (/^blob:http/.test(url)) {\n return url;\n }\n\n // Absolute URL. Accept only safe schemes, or no scheme.\n const schemes = Array.isArray(allowedSchemes) ? allowedSchemes.join('|') : 'http|https';\n const re = new RegExp('^((' + schemes + '):|//)', 'i');\n if (!re.test(url)) {\n return null;\n }\n\n return url;\n}\n\n// Ensure URL is suitable for field: the URL must be a relative URL or\n// have http:, https:, or data: scheme. In case of data: scheme, the URL must start with\n// a 'data:image/XXXX;base64,'.\nexport function sanitizeImageUrl(url) {\n if (!url) {\n return null;\n }\n\n const sanitizedUrl = sanitizeUrl(url);\n if (sanitizedUrl) {\n return sanitizedUrl;\n }\n\n // Is this a data: URL of an image?\n if (/data:image\\/[a-z0-9.-]+;base64,/i.test(url.trim())) {\n return url;\n }\n\n return null;\n}\n","// TagManager: edit topic or user tags.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport ChipInput from './chip-input.jsx';\n\nimport { MIN_TAG_LENGTH } from '../config.js';\nimport { arrayEqual } from '../lib/utils.js';\n\nexport default class TagManager extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n tags: this.props.tags,\n tagInput: '',\n activated: this.props.activated\n };\n\n this.handleTagInput = this.handleTagInput.bind(this);\n this.handleAddTag = this.handleAddTag.bind(this);\n this.handleRemoveTag = this.handleRemoveTag.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n static getDerivedStateFromProps(nextProps, prevState) {\n if (!arrayEqual(nextProps.tags, prevState.tags) && !prevState.activated) {\n return {tags: nextProps.tags};\n }\n return null;\n }\n\n handleTagInput(text) {\n this.setState({tagInput: text});\n if (text.length > 0) {\n const last = text[text.length-1];\n if (text[0] == '\"') {\n // This is a quoted string.\n if (text.length > 1 && last == '\"') {\n this.handleAddTag(text.substring(1, text.length-1));\n }\n } else if (last == ',' || last == ' ' || last == ';' || last == '\"') {\n // User entered ',', ' ' or ';'\n this.handleAddTag(text.substring(0, text.length-1).trim());\n }\n }\n }\n\n handleAddTag(tag) {\n if (tag.length > 0) {\n const tags = this.state.tags.slice(0);\n tags.push(tag);\n\n this.setState({tags: tags, tagInput: ''});\n if (this.props.onTagsChanged) {\n this.props.onTagsChanged(tags);\n }\n return tags;\n }\n return this.state.tags;\n }\n\n handleRemoveTag(tag, index) {\n const tags = this.state.tags.slice(0);\n tags.splice(index, 1);\n this.setState({tags: tags});\n if (this.props.onTagsChanged) {\n this.props.onTagsChanged(tags);\n }\n }\n\n handleSubmit() {\n // Add unprocessed input to tags, submit the list.\n this.props.onSubmit(this.handleAddTag(this.state.tagInput.trim()));\n this.setState({activated: false, tags: this.props.tags});\n }\n\n handleCancel() {\n this.setState({activated: false, tagInput: '', tags: this.props.tags});\n if (this.props.onCancel) {\n this.props.onCancel();\n }\n }\n\n render() {\n let tags = [];\n if (this.state.activated) {\n this.state.tags.map((tag) => {\n tags.push({user: tag, invalid: (tag.length < MIN_TAG_LENGTH)});\n });\n } else {\n this.state.tags.map((tag) => {\n tags.push({tag} );\n });\n if (tags.length == 0) {\n tags = (\n \n \n \n );\n }\n }\n return (\n \n
\n {this.props.title} \n
\n {this.state.activated ?\n
\n
{\n (add_tags_prompt) => \n } \n {this.props.onSubmit || this.props.onCancel ?\n
\n \n \n \n \n \n \n
\n : null}\n
\n :\n
\n }\n
\n );\n }\n};\n","// InfoView: panel with topic/user info.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport CheckBox from '../widgets/checkbox.jsx';\nimport ContactList from '../widgets/contact-list.jsx';\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport GroupManager from '../widgets/group-manager.jsx';\nimport InPlaceEdit from '../widgets/in-place-edit.jsx';\nimport MenuCancel from '../widgets/menu-cancel.jsx';\nimport MoreButton from '../widgets/more-button.jsx';\nimport PermissionsEditor from '../widgets/permissions-editor.jsx';\nimport TagManager from '../widgets/tag-manager.jsx';\n\nimport { MAX_TITLE_LENGTH, NO_ACCESS_MODE } from '../config.js';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\nimport { arrayEqual, vcard } from '../lib/utils.js';\n\nconst messages = defineMessages({\n requested: {\n id: 'requested_permissions',\n defaultMessage: 'Requested',\n description: 'Title for permissions'\n },\n granted: {\n id: 'granted_permissions',\n defaultMessage: 'Granted',\n description: 'Title for permissions'\n },\n edit_permissions: {\n id: 'menu_item_edit_permissions',\n defaultMessage: 'Edit permissions',\n description: 'Menu item [Edit permissions]'\n },\n other_user: {\n id: 'label_other_user',\n defaultMessage: 'Other',\n description: 'Label for the other user when the user is unnamed'\n },\n clear_messages: {\n id: 'action_clear_messages',\n defaultMessage: 'Clear Messages',\n description: 'Flat button [Clear Messages] (soft-delete messages)'\n },\n clear_messages_warning: {\n id: 'clear_messages_warning',\n defaultMessage: 'Are you sure you want to clear all messages? It cannot be undone.',\n description: 'Alert dialog warning when deleting all messages.'\n },\n delete_messages: {\n id: 'action_delete_messages',\n defaultMessage: 'Clear Messages for All',\n description: 'Flat button [Clear for All] (hard-delete all messages)'\n },\n delete_messages_warning: {\n id: 'delete_messages_warning',\n defaultMessage: 'Are you sure you want to delete all messages for everyone? It cannot be undone.',\n description: 'Alert dialog warning when hard-deleting all messages.'\n },\n leave_chat: {\n id: 'action_leave_chat',\n defaultMessage: 'Leave Conversation',\n description: 'Flat button [Leave Conversation]'\n },\n leave_chat_warning: {\n id: 'leave_chat_warning',\n defaultMessage: 'Are you sure you want to leave this conversation?',\n description: 'Alert dialog warning when unsubscribing from a chat.'\n },\n block_contact: {\n id: 'action_block_contact',\n defaultMessage: \"Block Contact\",\n description: \"Flat button [Block Contact]\"\n },\n block_contact_warning: {\n id: 'block_contact_warning',\n defaultMessage: 'Are you sure you want to block this contact?',\n description: 'Alert dialog warning when blocking a contact.'\n },\n report_chat: {\n id: 'action_report_chat',\n defaultMessage: 'Report Conversation',\n description: 'Flat button [Report Group]'\n },\n report_chat_warning: {\n id: 'report_chat_warning',\n defaultMessage: 'Are you sure you want to block and report this conversation?',\n description: 'Alert dialog warning when reporting a conversation for abuse'\n },\n});\n\nclass InfoView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n topic: null,\n owner: false,\n admin: false,\n sharer: false,\n deleter: false,\n muted: false,\n address: null,\n groupTopic: undefined,\n fullName: undefined,\n avatar: null,\n private: null,\n selectedContact: null,\n access: null,\n modeGiven: null,\n modeWant: null,\n modeGiven2: null, // P2P topic, the other user mode given\n modeWant2: null, // P2P topic, the other user mode want\n auth: null,\n anon: null,\n contactList: [],\n tags: [],\n showMemberPanel: false,\n showPermissionEditorFor: undefined,\n moreInfoExpanded: false,\n previousMetaDesc: undefined,\n previousSubsUpdated: undefined,\n previousTagsUpdated: undefined\n };\n\n this.resetSubs = this.resetSubs.bind(this);\n this.resetDesc = this.resetDesc.bind(this);\n this.onMetaDesc = this.onMetaDesc.bind(this);\n this.onSubsUpdated = this.onSubsUpdated.bind(this);\n this.onTagsUpdated = this.onTagsUpdated.bind(this);\n this.handleFullNameUpdate = this.handleFullNameUpdate.bind(this);\n this.handlePrivateUpdate = this.handlePrivateUpdate.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleMuted = this.handleMuted.bind(this);\n this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);\n this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);\n this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);\n this.handleShowAddMembers = this.handleShowAddMembers.bind(this);\n this.handleHideAddMembers = this.handleHideAddMembers.bind(this);\n this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);\n this.handleDeleteMessages = this.handleDeleteMessages.bind(this);\n this.handleLeave = this.handleLeave.bind(this);\n this.handleBlock = this.handleBlock.bind(this);\n this.handleReport = this.handleReport.bind(this);\n this.handleMemberSelected = this.handleMemberSelected.bind(this);\n this.handleMoreInfo = this.handleMoreInfo.bind(this);\n this.handleTagsUpdated = this.handleTagsUpdated.bind(this);\n this.handleContextMenu = this.handleContextMenu.bind(this);\n }\n\n // No need to separately handle component mount.\n componentDidUpdate(props) {\n const topic = this.props.tinode.getTopic(props.topic);\n if (!topic) {\n return;\n }\n\n if (this.onMetaDesc != topic.onMetaDesc) {\n this.previousMetaDesc = topic.onMetaDesc;\n topic.onMetaDesc = this.onMetaDesc;\n\n this.previousSubsUpdated = topic.onSubsUpdated;\n topic.onSubsUpdated = this.onSubsUpdated;\n\n if (topic.getType() == 'grp') {\n this.previousTagsUpdated = topic.onTagsUpdated;\n topic.onTagsUpdated = this.onTagsUpdated;\n } else {\n this.previousTagsUpdated = undefined;\n }\n }\n\n if (this.state.topic != props.topic) {\n this.setState({topic: props.topic});\n this.resetDesc(topic, props);\n this.resetSubs(topic, props);\n }\n }\n\n componentWillUnmount() {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.setState({topic: null});\n topic.onMetaDesc = this.previousMetaDesc;\n topic.onSubsUpdated = this.previousSubsUpdated;\n topic.onTagsUpdated = this.previousTagsUpdated;\n }\n\n resetSubs(topic, props) {\n const newState = {contactList: []};\n if (topic.getType() == 'p2p') {\n // Fetch the other party in the p2p conversation.\n // Topic may not be ready yet, so check if user is found.\n const user2 = topic.subscriber(props.topic);\n if (user2) {\n newState.modeGiven2 = user2.acs.getGiven();\n newState.modeWant2 = user2.acs.getWant();\n } else {\n newState.modeGiven2 = NO_ACCESS_MODE;\n newState.modeWant2 = NO_ACCESS_MODE;\n }\n } else {\n topic.subscribers((sub) => {\n newState.contactList.push(sub);\n }, this);\n }\n\n this.setState(newState);\n }\n\n resetDesc(topic, props) {\n const defacs = topic.getDefaultAccess() || {};\n const acs = topic.getAccessMode();\n\n this.setState({\n owner: acs && acs.isOwner(),\n admin: acs && acs.isAdmin(),\n sharer: acs && acs.isSharer(),\n deleter: acs && acs.isDeleter(),\n muted: acs && acs.isMuted(),\n\n fullName: topic.public ? topic.public.fn : undefined,\n avatar: makeImageUrl(topic.public ? topic.public.photo : null),\n private: topic.private ? topic.private.comment : null,\n address: topic.name,\n groupTopic: (topic.getType() == 'grp'),\n showMemberPanel: false,\n access: acs ? acs.getMode() : undefined,\n modeGiven: acs ? acs.getGiven() : undefined,\n modeWant: acs ? acs.getWant() : undefined,\n auth: defacs.auth,\n anon: defacs.anon\n });\n\n if (topic.getType() == 'grp' && acs && acs.isOwner()) {\n // Requesting tags: owner is editing the topic.\n topic.getMeta(topic.startMetaQuery().withTags().build());\n }\n }\n\n onMetaDesc(desc) {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.resetDesc(topic, this.props);\n\n if (this.previousMetaDesc && this.previousMetaDesc != this.onMetaDesc) {\n this.previousMetaDesc(desc);\n }\n }\n\n onSubsUpdated(subs) {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.resetSubs(topic, this.props);\n\n if (this.previousSubsUpdated && this.previousSubsUpdated != this.onSubsUpdated) {\n this.previousSubsUpdated(subs);\n }\n }\n\n onTagsUpdated(tags) {\n this.setState({tags: tags});\n\n if (this.previousTagsUpdated && this.previousTagsUpdated != this.onTagsUpdated) {\n this.previousTagsUpdated();\n }\n }\n\n handleFullNameUpdate(fn) {\n fn = fn.trim().substring(0, MAX_TITLE_LENGTH);\n if (this.state.fullName !== fn) {\n this.setState({fullName: fn});\n this.props.onTopicDescUpdate(this.props.topic, vcard(fn, null), null);\n }\n }\n\n handlePrivateUpdate(comment) {\n comment = comment.trim().substring(0, MAX_TITLE_LENGTH);\n if (this.state.private !== comment) {\n this.setState({private: comment});\n this.props.onTopicDescUpdate(this.props.topic, null, comment || Tinode.DEL_CHAR);\n }\n }\n\n handleImageChanged(img) {\n this.setState({avatar: img});\n this.props.onTopicDescUpdate(this.props.topic, vcard(null, img || Tinode.DEL_CHAR), null);\n }\n\n handleMuted(ignored, checked) {\n this.setState({muted: checked});\n this.props.onChangePermissions(this.props.topic, checked ? '-P' : '+P');\n }\n\n handlePermissionsChanged(perm) {\n switch (this.state.showPermissionEditorFor) {\n case 'auth':\n this.props.onTopicDescUpdate(this.props.topic, null, null, {auth: perm});\n break;\n case 'anon':\n this.props.onTopicDescUpdate(this.props.topic, null, null, {anon: perm});\n break;\n case 'mode':\n case 'want':\n this.props.onChangePermissions(this.props.topic, perm);\n break;\n case 'given':\n this.props.onChangePermissions(this.props.topic, perm, this.props.topic);\n break;\n case 'user':\n this.props.onChangePermissions(this.props.topic, perm, this.state.userPermissionsEdited);\n break;\n }\n\n this.setState({showPermissionEditorFor: undefined});\n }\n\n handleLaunchPermissionsEditor(which, uid) {\n const {formatMessage} = this.props.intl;\n let toEdit, toCompare, toSkip, titleEdit, titleCompare, userTitle, userAvatar;\n switch (which) {\n case 'mode':\n toEdit = this.state.access;\n break;\n case 'want':\n toEdit = this.state.modeWant;\n toCompare = this.state.modeGiven;\n toSkip = this.state.groupTopic ? 'O' : 'ASDO';\n titleEdit = formatMessage(messages.requested);\n titleCompare = formatMessage(messages.granted);\n break;\n case 'given':\n toEdit = this.state.modeGiven2;\n toCompare = this.state.modeWant2;\n toSkip = this.state.groupTopic ? (this.state.owner ? '' : 'O') : 'ASDO';\n titleEdit = formatMessage(messages.granted);\n titleCompare = formatMessage(messages.requested);\n break;\n case 'auth':\n toEdit = this.state.auth;\n toSkip = 'O';\n break;\n case 'anon':\n toEdit = this.state.anon;\n toSkip = 'O';\n break;\n case 'user': {\n let topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n var user = topic.subscriber(uid);\n if (!user || !user.acs) {\n return;\n }\n toEdit = user.acs.getGiven();\n toCompare = user.acs.getWant();\n toSkip = this.state.owner ? '' : 'O';\n titleEdit = formatMessage(messages.granted);\n titleCompare = formatMessage(messages.requested);\n if (user.public) {\n userTitle = user.public.fn;\n userAvatar = user.public.photo;\n }\n break;\n }\n default:\n console.log(\"Unknown permission editing mode '\" + which + \"'\");\n break;\n }\n this.setState({\n showPermissionEditorFor: which,\n userPermissionsEdited: uid,\n userPermissionsTitle: userTitle,\n userPermissionsAvatar: userAvatar,\n editedPermissions: toEdit,\n immutablePermissions: toCompare,\n editedPermissionsTitle: titleEdit,\n immutablePermissionsTitle: titleCompare,\n editedPermissionsSkipped: toSkip,\n });\n }\n\n handleHidePermissionsEditor() {\n this.setState({showPermissionEditorFor: undefined});\n }\n\n handleShowAddMembers(e) {\n e.preventDefault();\n this.props.onInitFind();\n this.setState({showMemberPanel: true});\n }\n\n handleHideAddMembers() {\n this.setState({showMemberPanel: false});\n }\n\n handleMemberUpdateRequest(members, added, removed) {\n this.props.onMemberUpdateRequest(this.props.topic, added, removed);\n this.setState({showMemberPanel: false});\n }\n\n handleDeleteMessages(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(this.state.deleter ? messages.delete_messages : messages.clear_messages), // title\n formatMessage(this.state.deleter ? messages.delete_messages_warning : messages.clear_messages_warning), // content\n (() => { this.props.onDeleteMessages(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleLeave(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.leave_chat), // title\n formatMessage(messages.leave_chat_warning), // content\n (() => { this.props.onLeaveTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleBlock(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.block_contact), // title\n formatMessage(messages.block_contact_warning), // content\n (() => { this.props.onBlockTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleReport(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.report_chat), // title\n formatMessage(messages.report_chat_warning), // content\n (() => { this.props.onReportTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleMemberSelected(uid) {\n this.setState({selectedContact: uid});\n }\n\n handleMoreInfo(open) {\n this.setState({moreInfoExpanded: open});\n }\n\n handleTagsUpdated(tags) {\n if (!arrayEqual(this.state.tags.slice(0), tags.slice(0))) {\n this.props.onTopicTagsUpdate(this.props.topic, tags);\n }\n }\n\n handleContextMenu(params) {\n const {formatMessage} = this.props.intl;\n const instance = this;\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n const user = topic.subscriber(params.topicName);\n if (!user || !user.acs) {\n return;\n }\n\n const menuItems = [\n {title: formatMessage(messages.edit_permissions), handler: function() {\n instance.handleLaunchPermissionsEditor('user', params.topicName);\n }},\n 'member_delete',\n user.acs.isMuted() ? 'member_unmute' : 'member_mute',\n user.acs.isJoiner() ? 'member_block' : 'member_unblock'\n ];\n this.props.showContextMenu({\n topicName: this.props.topic,\n x: params.x,\n y: params.y,\n user: params.topicName}, menuItems);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n\n return (\n \n
\n {this.props.displayMobile ?\n
: null}\n {this.state.showMemberPanel ?\n
\n :\n this.state.showPermissionEditorFor ?\n
\n :\n
\n
\n
\n
\n \n
\n
Unknown}\n readOnly={!this.state.owner}\n value={this.state.fullName}\n required={true}\n onFinished={this.handleFullNameUpdate} />
\n
\n \n \n \n
\n
\n {\n (private_placeholder) => \n } \n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n
\n
{\n (more) => \n } \n {this.state.moreInfoExpanded ?\n
\n
\n \n {this.state.address} \n
\n {this.state.groupTopic ?\n
\n \n \n \n \n {this.state.access}\n \n
\n :\n
\n
\n \n \n \n
\n
\n
\n \n {this.state.access}\n
\n
{this.state.fullName ? this.state.fullName : formatMessage(messages.other_user)}:\n \n {this.state.modeGiven2}\n \n
\n
\n
\n }\n {this.state.sharer && (this.state.auth || this.state.anon) ?\n
\n
\n \n \n \n
\n
\n
Auth: {this.state.admin ?\n \n {this.state.auth}\n \n :\n {this.state.auth} \n }\n
\n
Anon: {this.state.admin ?\n \n {this.state.anon}\n \n :\n {this.state.anon} \n }\n
\n
\n
\n :\n null\n }\n
\n :\n null\n }\n
\n
\n {this.state.owner ?\n <>\n
{\n (tags) => \n } \n
\n >\n :\n null\n }\n
\n {this.state.groupTopic ?\n <>\n
\n
\n
\n \n \n \n
\n
\n
{\n (no_members) => \n } \n
\n >\n :\n null\n }\n
\n }\n
\n );\n }\n};\n\nexport default injectIntl(InfoView);\n","// File uload/download progress indicator with a cancel inside.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class FileProgress extends React.PureComponent {\n render() {\n return (\n \n );\n }\n}\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport FileProgress from './file-progress.jsx';\nimport { bytesToHumanSize } from '../lib/strformat.js';\nimport { isUrlRelative, sanitizeUrl } from '../lib/utils.js';\n\nexport default class Attachment extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n downloader: null,\n progress: 0\n };\n\n this.downloadFile = this.downloadFile.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n downloadFile(url, filename, mimetype) {\n var downloader = this.props.tinode.getLargeFileHelper();\n this.setState({downloader: downloader});\n downloader.download(url, filename, mimetype, (loaded) => {\n this.setState({progress: loaded / this.props.size});\n }).then(() => {\n this.setState({downloader: null, progress: 0});\n }).catch((err) => {\n if (err) {\n this.props.onError(\"Error downloading file: \" + err.message, 'err');\n }\n this.setState({downloader: null, progress: 0});\n });\n }\n\n handleCancel() {\n if (this.props.uploader) {\n this.props.onCancelUpload();\n } else if (this.state.downloader) {\n this.state.downloader.cancel();\n }\n }\n\n render() {\n let filename = this.props.filename || 'file_attachment';\n if (filename.length > 36) {\n filename = filename.substr(0, 16) + '...' + filename.substr(-16);\n }\n let size = this.props.size > 0 ?\n ({bytesToHumanSize(this.props.size)}) :\n null;\n\n // Detect if the download URL is relative or absolute.\n // If the URL is relative use LargeFileHelper to attach authentication\n // credentials to the request.\n let url, helperFunc;\n if (!this.props.uploader && !this.state.downloader && isUrlRelative(this.props.downloadUrl)) {\n // Relative URL. Use download helper.\n url = '#';\n helperFunc = (e) => {\n e.preventDefault();\n this.downloadFile(this.props.downloadUrl, this.props.filename, this.props.mimetype);\n };\n } else {\n url = sanitizeUrl(this.props.downloadUrl) || 'about:blank';\n helperFunc = null;\n }\n return (\n \n
insert_drive_file
\n
\n
{filename} {size}
\n {this.props.uploader || this.state.downloader ?\n
\n :\n
\n }\n
\n
\n );\n }\n};\n","// Received/read indicator.\nimport React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport { shortDateFormat } from '../lib/strformat.js';\n\nconst messages = defineMessages({\n 'sending': {\n 'id': 'message_sending',\n 'defaultMessage': 'sending...',\n 'description': 'Message being sent, in place of time stamp'\n },\n 'failed': {\n 'id': 'message_sending_failed',\n 'defaultMessage': 'failed',\n 'description': 'Failed to send message, in place of time stamp'\n }\n});\n\nclass ReceivedMarker extends React.PureComponent {\n render() {\n const {formatMessage} = this.props.intl;\n let timestamp;\n if (this.props.received <= Tinode.MESSAGE_STATUS_SENDING) {\n timestamp = formatMessage(messages.sending);\n } else if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {\n timestamp = formatMessage(messages.failed);\n } else {\n timestamp = shortDateFormat(this.props.timestamp, this.props.intl.locale);\n }\n\n let marker = null;\n if (this.props.received <= Tinode.MESSAGE_STATUS_SENDING) {\n marker = (access_time ); // watch face\n } else if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {\n marker = (warning ); // yellow icon /!\\\n } else if (this.props.received == Tinode.MESSAGE_STATUS_SENT) {\n marker = (done ); // checkmark\n } else if (this.props.received == Tinode.MESSAGE_STATUS_RECEIVED) {\n marker = (done_all ); // double checkmark\n } else if (this.props.received == Tinode.MESSAGE_STATUS_READ) {\n marker = (done_all ); // blue double checkmark\n }\n\n return (\n \n {timestamp}{'\\u00a0'}{marker}\n \n );\n }\n};\n\nexport default injectIntl(ReceivedMarker);\n","// Single message, sent or received.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\nimport { Drafty } from 'tinode-sdk'\n\nimport Attachment from './attachment.jsx';\nimport LetterTile from './letter-tile.jsx';\nimport ReceivedMarker from './received-marker.jsx'\nimport { sanitizeImageUrl, sanitizeUrl } from '../lib/utils.js';\n\nexport default class ChatMessage extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n progress: 0\n };\n\n if (props.uploader) {\n props.uploader.onProgress = this.handleProgress.bind(this);\n }\n\n this.handlePreviewImage = this.handlePreviewImage.bind(this);\n this.handleFormButtonClick = this.handleFormButtonClick.bind(this);\n this.handleContextClick = this.handleContextClick.bind(this);\n this.handleCancelUpload = this.handleCancelUpload.bind(this);\n }\n\n handlePreviewImage(e) {\n e.preventDefault();\n this.props.onImagePreview({\n url: e.target.src,\n filename: e.target.title,\n width: e.target.dataset.width,\n height: e.target.dataset.height,\n size: e.target.dataset.size,\n type: e.target.dataset.mime\n });\n }\n\n handleFormButtonClick(e) {\n e.preventDefault();\n const data = {\n seq: this.props.seq\n };\n data.resp = {};\n if (e.target.dataset.name) {\n data.resp[e.target.dataset.name] = e.target.dataset.val ? e.target.dataset.val :\n e.target.dataset.val === undefined ? 1 : '' + e.target.dataset.val;\n }\n if (e.target.dataset.act == 'url') {\n data.ref = sanitizeUrl(e.target.dataset.ref) || 'about:blank';\n }\n const text = e.target.dataset.title || 'unknown';\n this.props.onFormResponse(e.target.dataset.act, text, data);\n }\n\n handleContextClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.showContextMenu({ seq: this.props.seq, y: e.pageY, x: e.pageX });\n }\n\n handleProgress(ratio) {\n this.setState({progress: ratio});\n }\n\n handleCancelUpload() {\n this.props.uploader.cancel();\n }\n\n render() {\n const sideClass = this.props.deleted ? 'center' :\n (this.props.sequence + ' ' + (this.props.response ? 'left' : 'right'));\n const bubbleClass = (this.props.sequence == 'single' || this.props.sequence == 'last') ? 'bubble tip' : 'bubble';\n const avatar = this.props.deleted ? null : (this.props.userAvatar || true);\n const fullDisplay = (this.props.userFrom && this.props.response &&\n (this.props.sequence == 'single' || this.props.sequence == 'last'));\n\n let content = this.props.content;\n const attachments = [];\n if (this.props.mimeType == Drafty.getContentType()) {\n Drafty.attachments(content, function(att, i) {\n if (att.mime == 'application/json') {\n // Don't show json objects as attachments.\n // They are not meant for users.\n return;\n }\n attachments.push( );\n }, this);\n content = React.createElement('span', null, Drafty.format(content, draftyFormatter, this));\n } else if (this.props.deleted) {\n // Message represents a range of deleted messages.\n content = <>block \n \n >\n } else if (typeof content != 'string') {\n content = <>error_outline \n \n >\n }\n\n return (\n \n {this.props.userFrom && this.props.response ?\n \n {fullDisplay ?\n :\n null}\n
:\n null}\n \n
\n
\n {content}\n {attachments}\n {this.props.timestamp ?\n \n : null}\n
\n {this.props.deleted ?\n null :\n
\n \n expand_more \n \n \n }\n
\n {fullDisplay ?\n
\n {\n (notFound) => {return this.props.userName || {notFound} }\n } \n
\n : null\n }\n
\n \n );\n }\n};\n\n// Convert Drafty object to a tree of React elements.\nimport { BROKEN_IMAGE_SIZE, REM_SIZE } from '../config.js';\nimport { fitImageSize } from '../lib/blob-helpers.js';\n\nfunction draftyFormatter(style, data, values, key) {\n let el = Drafty.tagName(style);\n if (el) {\n let attr = Drafty.attrValue(style, data) || {};\n attr.key = key;\n switch (style) {\n case 'IM':\n // Additional processing for images\n if (data) {\n attr.className = 'inline-image';\n let dim = fitImageSize(data.width, data.height,\n Math.min(this.props.viewportWidth - REM_SIZE * 4, REM_SIZE * 36), REM_SIZE * 24, false);\n dim = dim || {dstWidth: BROKEN_IMAGE_SIZE, dstHeight: BROKEN_IMAGE_SIZE};\n attr.style = { width: dim.dstWidth + 'px', height: dim.dstHeight + 'px' };\n attr.src = sanitizeImageUrl(attr.src);\n if (attr.src) {\n attr.onClick = this.handlePreviewImage;\n attr.className += ' image-clickable';\n } else {\n attr.src = 'img/broken_image.png';\n }\n }\n break;\n case 'BN':\n // Button\n attr.onClick = this.handleFormButtonClick;\n let inner = React.Children.map(values, (child) => {\n return typeof child == 'string' ? child : undefined;\n });\n if (!inner || inner.length == 0) {\n inner = [attr.name]\n }\n // Get text which will be sent back when the button is clicked.\n attr['data-title'] = inner.join('');\n break;\n case 'FM':\n // Form\n attr.className = 'bot-form';\n break;\n case 'FE':\n // Form element formatting is dependent on element content.\n break;\n }\n return React.createElement(el, attr, values);\n } else {\n return values;\n }\n};\n","// GroupSubs: a list of group subscribers currently online.\n// Show in the topic title bar\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport { MAX_ONLINE_IN_TOPIC } from '../config.js'\n\nimport LetterTile from './letter-tile.jsx';\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\nexport default class GroupSubs extends React.Component {\n constructor(props) {\n super(props);\n }\n\n render() {\n const usersOnline = [];\n const totalCount = (this.props.subscribers || []).length;\n const countToShow = Math.min(MAX_ONLINE_IN_TOPIC, totalCount);\n\n (this.props.subscribers || []).some((sub) => {\n usersOnline.push(\n \n \n
\n );\n return usersOnline.length == countToShow;\n });\n\n return (\n {usersOnline} {totalCount > countToShow ?\n \n \n : null}\n
\n );\n }\n};\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport { REM_SIZE } from '../config.js';\nimport { fitImageSize } from '../lib/blob-helpers.js';\nimport { bytesToHumanSize } from '../lib/strformat.js';\n\nexport default class ImagePreview extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n width: 0,\n height: 0\n };\n }\n\n assignWidth(node) {\n if (node && !this.state.width) {\n const bounds = node.getBoundingClientRect();\n this.setState({\n width: bounds.width | 0,\n height: bounds.height | 0\n });\n }\n }\n\n render() {\n if (!this.props.content) {\n return null;\n }\n\n const dim = fitImageSize(this.props.content.width, this.props.content.height,\n this.state.width, this.state.height, false);\n const size = dim ? { width: dim.dstWidth + 'px', height: dim.dstHeight + 'px' } :\n ((this.props.content.width > this.props.content.height) ? {width: '100%'} : {height: '100%'});\n size.maxWidth = '100%';\n size.maxHeight = '100%';\n\n let filename = this.props.content.filename;\n // Averate font aspect ratio is ~0.5; File name takes 1/3 of the viewport width.\n const maxlength = Math.max(((this.state.width / REM_SIZE / 1.5) | 0) - 2, 12);\n if (filename.length > maxlength) {\n filename = filename.slice(0, maxlength/2 - 1) + '...' + filename.slice(1 - maxlength/2);\n }\n const width = this.props.content.width || '-';\n const height = this.props.content.height || '-';\n return (\n \n
\n
this.assignWidth(node)}>\n
\n
\n \n
\n );\n }\n};\n","// A single topic or user.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class Invitation extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleButtonAction = this.handleButtonAction.bind(this);\n }\n\n handleButtonAction(evt, data) {\n evt.preventDefault();\n this.props.onAction(data);\n }\n\n render() {\n return (\n \n
\n \n
\n
\n { this.handleButtonAction(event, \"accept\"); }}>\n \n \n { this.handleButtonAction(event, \"delete\"); }}>\n \n \n { this.handleButtonAction(event, \"block\"); }}>\n \n \n
\n
\n );\n }\n};\n","import React from 'react';\n\nexport default class LoadSpinner extends React.PureComponent {\n render() {\n return (this.props.show ?\n : null);\n }\n}\n","/* This is just a static page to display when no conversation is selected. */\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport { APP_NAME } from '../config.js';\n\nexport default class LogoView extends React.PureComponent {\n render() {\n var version = APP_NAME + ' (' + Tinode.getLibrary() + ')';\n return (\n \n
\n
\n \n Tinode Web \n \n
{version}
\n
{this.props.serverVersion} ({this.props.serverAddress})
\n
\n
\n );\n }\n};\n","// Send message form.\nimport React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\nimport { Drafty } from 'tinode-sdk';\n\nimport { KEYPRESS_DELAY, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';\nimport { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64, imageFileScaledToBase64 } from '../lib/blob-helpers.js';\nimport { bytesToHumanSize } from '../lib/strformat.js';\n\nconst messages = defineMessages({\n 'messaging_disabled': {\n id: 'messaging_disabled_prompt',\n defaultMessage: 'Messaging disabled',\n description: 'Prompt in SendMessage in read-only topic'\n },\n 'type_new_message': {\n id: 'new_message_prompt',\n defaultMessage: 'New message',\n description: 'Prompt in SendMessage in read-only topic'\n },\n 'file_attachment_too_large': {\n id: 'file_attachment_too_large',\n defaultMessage: 'The file size {size} exceeds the {limit} limit.',\n description: 'Error message when attachment is too large'\n },\n 'cannot_initiate_upload': {\n id: 'cannot_initiate_file_upload',\n defaultMessage: 'Cannot initiate file upload.',\n description: 'Generic error messagewhen attachment fails'\n },\n});\n\nclass SendMessage extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n message: '',\n // Make initial keypress time as if it happened 5001 milliseconds in the past.\n keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1\n };\n\n this.handlePasteEvent = this.handlePasteEvent.bind(this);\n this.handleAttachImage = this.handleAttachImage.bind(this);\n this.handleAttachFile = this.handleAttachFile.bind(this);\n this.handleSend = this.handleSend.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleMessageTyping = this.handleMessageTyping.bind(this);\n }\n\n componentDidMount() {\n this.messageEditArea.addEventListener('paste', this.handlePasteEvent, false);\n }\n\n componentWillUnmount() {\n this.messageEditArea.removeEventListener('paste', this.handlePasteEvent, false);\n }\n\n componentDidUpdate() {\n this.messageEditArea.focus();\n }\n\n handlePasteEvent(e) {\n if (this.props.disabled) {\n return;\n }\n // FIXME: handle large files too.\n if (filePasted(e,\n (bits, mime, width, height, fname) => {\n this.props.onAttachImage(mime, bits, width, height, fname);\n },\n (mime, bits, fname) => {\n this.props.onAttachFile(mime, bits, fname);\n },\n this.props.onError)) {\n\n // If a file was pasted, don't paste base64 data into input field.\n e.preventDefault();\n }\n }\n\n handleAttachImage(e) {\n if (e.target.files && e.target.files.length > 0) {\n const file = e.target.files[0];\n // Check if the uploaded file is indeed an image and if it isn't too large.\n if (file.size > MAX_INBAND_ATTACHMENT_SIZE || SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) {\n // Convert image for size or format.\n imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false,\n // Success\n (bits, mime, width, height, fname) => {\n this.props.onAttachImage(mime, bits, width, height, fname);\n },\n // Failure\n (err) => {\n this.props.onError(err, 'err');\n });\n } else {\n // Image can be uploaded as is. No conversion is needed.\n imageFileToBase64(file,\n // Success\n (bits, mime, width, height, fname) => {\n this.props.onAttachImage(mime, bits, width, height, fname);\n },\n // Failure\n (err) => {\n this.props.onError(err, 'err');\n }\n );\n }\n }\n // Clear the value so the same file can be uploaded again.\n e.target.value = '';\n }\n\n handleAttachFile(e) {\n const {formatMessage} = this.props.intl;\n if (e.target.files && e.target.files.length > 0) {\n const file = e.target.files[0];\n if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) {\n // Too large.\n this.props.onError(formatMessage(messages.file_attachment_too_large,\n {size: bytesToHumanSize(file.size), limit: bytesToHumanSize(MAX_EXTERN_ATTACHMENT_SIZE)}), 'err');\n } else if (file.size > MAX_INBAND_ATTACHMENT_SIZE) {\n // Too large to send inband - uploading out of band and sending as a link.\n const uploader = this.props.tinode.getLargeFileHelper();\n if (!uploader) {\n this.props.onError(formatMessage(messages.cannot_initiate_upload));\n return;\n }\n // Pass data and the uploader to the TinodeWeb.\n this.props.onAttachFile(file.type, null, file.name, file.size, uploadCompletionPromise, uploader);\n } else {\n // Small enough to send inband.\n fileToBase64(file,\n (mime, bits, fname) => {\n this.props.onAttachFile(mime, bits, fname);\n },\n this.props.onError\n );\n }\n }\n // Clear the value so the same file can be uploaded again.\n e.target.value = '';\n }\n\n handleSend(e) {\n e.preventDefault();\n const message = this.state.message.trim();\n if (message) {\n this.props.onSendMessage(this.state.message.trim());\n this.setState({message: ''});\n }\n }\n\n /* Send on Enter key */\n handleKeyPress(e) {\n // Remove this if you don't want Enter to trigger send\n if (e.key === 'Enter') {\n // Have Shift-Enter insert a line break instead\n if (!e.shiftKey) {\n e.preventDefault();\n e.stopPropagation();\n\n this.handleSend(e);\n }\n }\n }\n\n handleMessageTyping(e) {\n const newState = {message: e.target.value};\n const now = new Date().getTime();\n if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (topic.isSubscribed()) {\n topic.noteKeyPress();\n }\n newState.keypressTimestamp = now;\n }\n this.setState(newState);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const prompt = this.props.disabled ?\n formatMessage(messages.messaging_disabled) :\n formatMessage(messages.type_new_message);\n return (\n \n );\n }\n};\n\nexport default injectIntl(SendMessage);\n","import React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\nconst Drafty = Tinode.Drafty;\n\nimport ChatMessage from '../widgets/chat-message.jsx';\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport GroupSubs from '../widgets/group-subs.jsx';\nimport ImagePreview from '../widgets/image-preview.jsx';\nimport Invitation from '../widgets/Invitation.jsx';\nimport LetterTile from '../widgets/letter-tile.jsx';\nimport LoadSpinner from '../widgets/load-spinner.jsx';\nimport LogoView from './logo-view.jsx';\nimport SendMessage from '../widgets/send-message.jsx';\n\nimport { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE } from '../config.js';\nimport { makeImageUrl } from '../lib/blob-helpers.js';\nimport { shortDateFormat } from '../lib/strformat.js';\n\nconst messages = defineMessages({\n online_now: {\n id: 'online_now',\n defaultMessage: 'online now',\n description: 'Indicator that the user or topic is currently online',\n },\n last_seen: {\n id: 'last_seen_timestamp',\n defaultMessage: 'Last seen',\n description: 'Label for the timestamp of when the user or topic was last online'\n },\n 'not_found': {\n id: 'title_not_found',\n defaultMessage: 'Not found',\n description: 'Title shown when topic is not found'\n }\n});\n\n// Checks if the access permissions are granted but not yet accepted.\nfunction isUnconfirmed(acs) {\n if (acs) {\n const ex = acs.getExcessive() || '';\n return acs.isJoiner('given') && (ex.includes('R') || ex.includes('W'));\n }\n return false;\n}\n\nfunction isPeerRestricted(acs) {\n if (acs) {\n const ms = acs.getMissing() || '';\n return acs.isJoiner('want') && (ms.includes('R') || ms.includes('W'));\n }\n return false;\n}\n\nclass MessagesView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = MessagesView.getDerivedStateFromProps(props, {});\n\n // this.propsChange = this.propsChange.bind(this);\n this.leave = this.leave.bind(this);\n this.handleScrollReference = this.handleScrollReference.bind(this);\n this.handleScrollEvent = this.handleScrollEvent.bind(this);\n this.handleDescChange = this.handleDescChange.bind(this);\n this.handleSubsUpdated = this.handleSubsUpdated.bind(this);\n this.handleNewMessage = this.handleNewMessage.bind(this);\n this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);\n this.handleInfoReceipt = this.handleInfoReceipt.bind(this);\n this.handleImagePreview = this.handleImagePreview.bind(this);\n this.handleCloseImagePreview = this.handleCloseImagePreview.bind(this);\n this.handleFormResponse = this.handleFormResponse.bind(this);\n this.handleContextClick = this.handleContextClick.bind(this);\n this.handleShowContextMenuMessage = this.handleShowContextMenuMessage.bind(this);\n this.handleNewChatAcceptance = this.handleNewChatAcceptance.bind(this);\n this.handleEnablePeer = this.handleEnablePeer.bind(this);\n this.handleAttachFile = this.handleAttachFile.bind(this);\n this.handleAttachImage = this.handleAttachImage.bind(this);\n }\n\n componentDidMount() {\n // this.propsChange(this.props, this.state);\n if (this.messagesScroller) {\n this.messagesScroller.addEventListener('scroll', this.handleScrollEvent);\n }\n }\n\n componentWillUnmount() {\n if (this.messagesScroller) {\n this.messagesScroller.removeEventListener('scroll', this.handleScrollEvent);\n }\n }\n\n // Scroll last message into view on component update e.g. on message received\n // or vertical shrinking.\n componentDidUpdate(prevProps, prevState) {\n if (this.messagesScroller) {\n if (prevState.topic != this.state.topic || prevState.messages.length != this.state.messages.length) {\n this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition;\n } else if (prevProps.viewportHeight > this.props.viewportHeight) {\n this.messagesScroller.scrollTop += prevProps.viewportHeight - this.props.viewportHeight;\n }\n }\n\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (this.state.topic != prevState.topic) {\n if (prevState.topic && !Tinode.isNewGroupTopicName(prevState.topic)) {\n this.leave(prevState.topic);\n this.props.readTimerHandler(null);\n }\n\n if (topic) {\n topic.onData = this.handleNewMessage;\n topic.onAllMessagesReceived = this.handleAllMessagesReceived;\n topic.onInfo = this.handleInfoReceipt;\n topic.onMetaDesc = this.handleDescChange;\n topic.onSubsUpdated = this.handleSubsUpdated;\n topic.onPres = this.handleSubsUpdated;\n }\n }\n\n if (topic && !topic.isSubscribed() && this.props.ready &&\n ((this.state.topic != prevState.topic) || !prevProps.ready)) {\n // Is this a new topic?\n const newTopic = (this.props.newTopicParams && this.props.newTopicParams._topicName == this.props.topic);\n\n // Don't request the tags. They are useless unless the user\n // is the owner and is editing the topic.\n let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub();\n if (this.state.isReader || newTopic) {\n // If reading is either permitted or we don't know because it's a new topic. Ask for messages.\n getQuery = getQuery.withLaterData(MESSAGES_PAGE);\n if (this.state.isReader) {\n getQuery = getQuery.withLaterDel();\n }\n // And show \"loading\" spinner.\n this.setState({ fetchingMessages: true });\n }\n const setQuery = newTopic ? this.props.newTopicParams : undefined;\n topic.subscribe(getQuery.build(), setQuery)\n .then((ctrl) => {\n if (this.state.topic != ctrl.topic) {\n this.setState({topic: ctrl.topic});\n }\n this.props.onNewTopicCreated(this.props.topic, ctrl.topic);\n // If there are unsent messages, try sending them now.\n topic.queuedMessages((pub) => {\n if (!pub._sending && topic.isSubscribed()) {\n topic.publishMessage(pub);\n }\n });\n })\n .catch((err) => {\n console.log(\"Failed subscription to\", this.state.topic);\n this.props.onError(err.message, 'err');\n const blankState = MessagesView.getDerivedStateFromProps({}, {});\n blankState.title = this.props.intl.formatMessage(messages.not_found);\n this.setState(blankState);\n });\n }\n }\n\n static getDerivedStateFromProps(nextProps, prevState) {\n let nextState = {};\n if (!nextProps.topic) {\n // Default state: no topic.\n nextState = {\n messages: [],\n onlineSubs: [],\n topic: null,\n title: '',\n avatar: null,\n imagePreview: null,\n typingIndicator: false,\n scrollPosition: 0,\n fetchingMessages: false,\n peerMessagingDisabled: false\n };\n } else if (nextProps.topic != prevState.topic) {\n const topic = nextProps.tinode.getTopic(nextProps.topic);\n nextState = {\n topic: nextProps.topic,\n imagePreview: null,\n typingIndicator: false,\n scrollPosition: 0,\n fetchingMessages: false\n };\n\n if (topic) {\n // Topic exists.\n const msgs = [];\n const subs = [];\n\n if (nextProps.connected) {\n topic.subscribers((sub) => {\n if (sub.online && sub.user != nextProps.myUserId) {\n subs.push(sub);\n }\n });\n }\n\n topic.messages(function(msg) {\n if (!msg.deleted) {\n msgs.push(msg);\n }\n });\n\n Object.assign(nextState, {\n messages: msgs,\n onlineSubs: subs\n });\n\n if (topic.public) {\n Object.assign(nextState, {\n title: topic.public.fn,\n avatar: makeImageUrl(topic.public.photo)\n });\n } else {\n Object.assign(nextState, {\n title: '',\n avatar: null\n });\n }\n\n const peer = topic.p2pPeerDesc();\n if (peer) {\n Object.assign(nextState, {\n peerMessagingDisabled: isPeerRestricted(peer.acs)\n });\n } else if (prevState.peerMessagingDisabled) {\n Object.assign(nextState, {\n peerMessagingDisabled: false\n });\n }\n } else {\n // Invalid topic.\n Object.assign(nextState, {\n messages: [],\n onlineSubs: [],\n title: '',\n avatar: null,\n peerMessagingDisabled: false\n });\n }\n }\n\n if (nextProps.acs) {\n if (nextProps.acs.isWriter() != prevState.isWriter) {\n nextState.isWriter = !prevState.isWriter;\n }\n if (nextProps.acs.isReader() != prevState.isReader) {\n nextState.isReader = !prevState.isReader;\n }\n if (!nextProps.acs.isReader('given') != prevState.readingBlocked) {\n nextState.readingBlocked = !prevState.readingBlocked;\n }\n } else {\n if (prevState.isWriter) {\n nextState.isWriter = false;\n }\n if (prevState.isReader) {\n nextState.isReader = false;\n }\n if (!prevState.readingBlocked) {\n prevState.readingBlocked = true;\n }\n }\n\n if (isUnconfirmed(nextProps.acs) == !prevState.unconformed) {\n nextState.unconfirmed = !prevState.unconformed;\n }\n\n // Clear subscribers online when there is no connection.\n if (!nextProps.connected && prevState.onlineSubs && prevState.onlineSubs.length > 0) {\n nextState.onlineSubs = [];\n }\n\n return nextState;\n }\n\n leave(oldTopicName) {\n if (!oldTopicName) {\n return;\n }\n let oldTopic = this.props.tinode.getTopic(oldTopicName);\n if (oldTopic && oldTopic.isSubscribed()) {\n oldTopic.leave(false)\n .catch(() => { /* do nothing here */ })\n .finally(() => {\n // We don't care if the request succeeded or failed.\n // The topic is dead regardless.\n this.setState({fetchingMessages: false});\n oldTopic.onData = undefined;\n oldTopic.onAllMessagesReceived = undefined;\n oldTopic.onInfo = undefined;\n oldTopic.onMetaDesc = undefined;\n oldTopic.onSubsUpdated = undefined;\n oldTopic.onPres = undefined;\n });\n }\n }\n\n handleScrollReference(node) {\n if (node) {\n node.addEventListener('scroll', this.handleScrollEvent);\n this.messagesScroller = node;\n }\n }\n\n // Get older messages\n handleScrollEvent(event) {\n this.setState({scrollPosition: event.target.scrollHeight - event.target.scrollTop});\n if (event.target.scrollTop <= 0) {\n this.setState((prevState, props) => {\n const newState = {};\n if (!prevState.fetchingMessages) {\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (topic && topic.isSubscribed() && topic.msgHasMoreMessages()) {\n newState.fetchingMessages = true;\n topic.getMessagesPage(MESSAGES_PAGE).catch((err) => {\n this.setState({fetchingMessages: false});\n this.props.onError(err.message, 'err');\n });\n }\n }\n return newState;\n });\n }\n }\n\n handleDescChange(desc) {\n if (desc.public) {\n this.setState({\n title: desc.public.fn,\n avatar: makeImageUrl(desc.public.photo)\n });\n } else {\n this.setState({\n title: '',\n avatar: null\n });\n }\n\n if (desc.acs) {\n this.setState({\n isWriter: desc.acs.isWriter(),\n isReader: desc.acs.isReader(),\n readingBlocked: !desc.acs.isReader('given'),\n unconfirmed: isUnconfirmed(desc.acs),\n });\n }\n }\n\n handleSubsUpdated() {\n if (this.state.topic) {\n const subs = [];\n const topic = this.props.tinode.getTopic(this.state.topic);\n topic.subscribers((sub) => {\n if (sub.online && sub.user != this.props.myUserId) {\n subs.push(sub);\n }\n });\n const newState = {onlineSubs: subs};\n const peer = topic.p2pPeerDesc();\n if (peer) {\n Object.assign(newState, {\n peerMessagingDisabled: isPeerRestricted(peer.acs)\n });\n } else if (this.state.peerMessagingDisabled) {\n Object.assign(newState, {\n peerMessagingDisabled: false\n });\n }\n this.setState(newState);\n }\n }\n\n handleNewMessage(msg) {\n // Regenerate messages list\n const topic = this.props.tinode.getTopic(this.state.topic);\n const newState = {messages: []};\n topic.messages((m) => {\n if (!m.deleted) {\n newState.messages.push(m);\n }\n });\n\n // msg could be null if one or more messages were deleted.\n if (msg && !msg.deleted) {\n // If the message is added to the end of the message list,\n // scroll to the bottom.\n if (topic.isNewMessage(msg.seq)) {\n newState.scrollPosition = 0;\n }\n\n // Aknowledge messages except own messages. They are\n // automatically assumed to be read and recived.\n const status = topic.msgStatus(msg);\n if (status >= Tinode.MESSAGE_STATUS_SENT && msg.from != this.props.myUserId) {\n this.props.readTimerHandler(() => {\n topic.noteRead(msg.seq);\n });\n }\n this.props.onData(msg);\n }\n this.setState(newState);\n }\n\n handleAllMessagesReceived(count) {\n this.setState({fetchingMessages: false});\n }\n\n handleInfoReceipt(info) {\n switch (info.what) {\n case 'kp': {\n clearTimeout(this.keyPressTimer);\n var instance = this;\n this.keyPressTimer = setTimeout(function() {\n instance.setState({typingIndicator: false});\n }, KEYPRESS_DELAY + 1000);\n if (!this.state.typingIndicator) {\n this.setState({typingIndicator: true});\n }\n break;\n }\n case 'read':\n case 'recv':\n // Redraw due to changed recv/read status.\n this.forceUpdate();\n break;\n default:\n console.log(\"Other change in topic: \", info.what);\n }\n }\n\n handleImagePreview(content) {\n this.setState({ imagePreview: content });\n }\n\n handleCloseImagePreview() {\n this.setState({ imagePreview: null });\n }\n\n handleFormResponse(action, text, data) {\n if (action == 'pub') {\n this.props.sendMessage(Drafty.attachJSON(Drafty.parse(text), data));\n } else if (action == 'url') {\n const url = new URL(data.ref);\n const params = url.searchParams;\n for (let key in data.resp) {\n if (data.resp.hasOwnProperty(key)) {\n params.set(key, data.resp[key]);\n }\n }\n ['name', 'seq'].map(function(key) {\n if (data[key]) {\n params.set(key, data[key]);\n }\n });\n params.set('uid', this.props.myUserId);\n url.search = params;\n window.open(url, '_blank');\n } else {\n console.log(\"Unknown action in form\", action);\n }\n }\n\n handleContextClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.showContextMenu({ topicName: this.state.topic, y: e.pageY, x: e.pageX });\n }\n\n handleShowContextMenuMessage(params) {\n params.topicName = this.state.topic;\n const menuItems = ['message_delete'];\n const topic = this.props.tinode.getTopic(params.topicName);\n if (topic) {\n const acs = topic.getAccessMode();\n if (acs && acs.isDeleter()) {\n menuItems.push('message_delete_hard');\n }\n }\n this.props.showContextMenu(params, menuItems);\n }\n\n handleNewChatAcceptance(action) {\n this.props.onNewChat(this.state.topic, action);\n }\n\n handleEnablePeer(e) {\n e.preventDefault();\n this.props.onChangePermissions(this.state.topic, DEFAULT_P2P_ACCESS_MODE, this.state.topic);\n }\n\n handleAttachFile(mime, bits, fname, size, uploadCompletionPromise, uploader) {\n if (uploadCompletionPromise) {\n // Format data and initiate upload.\n const msg = Drafty.attachFile(null, mime, null, fname, size, uploadCompletionPromise);\n // Pass data and the uploader to the TinodeWeb.\n this.props.sendMessage(msg, uploadCompletionPromise, uploader);\n } else {\n this.props.sendMessage(Drafty.attachFile(null, mime, bits, fname));\n }\n }\n\n handleAttachImage(mime, bits, width, height, fname) {\n this.props.sendMessage(Drafty.insertImage(null, 0, mime, bits, width, height, fname));\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n\n let component;\n if (this.state.topic) {\n const topic = this.props.tinode.getTopic(this.state.topic);\n const groupTopic = topic.getType() == 'grp';\n let messageNodes = [];\n let previousFrom = null;\n let chatBoxClass = null;\n for (let i=0; i \n );\n }\n\n let lastSeen = null;\n const cont = this.props.tinode.getMeTopic().getContact(this.state.topic);\n if (cont && Tinode.topicType(cont.topic) == 'p2p') {\n if (cont.online) {\n lastSeen = formatMessage(messages.online_now);\n } else if (cont.seen) {\n lastSeen = formatMessage(messages.last_seen) + \": \" +\n shortDateFormat(cont.seen.when, this.props.intl.locale);\n // TODO: also handle user agent in c.seen.ua\n }\n }\n const avatar = this.state.avatar || true;\n const online = this.props.online ? 'online' + (this.state.typingIndicator ? ' typing' : '') : 'offline';\n\n component = (\n \n
\n {this.props.displayMobile ?\n
\n : null}\n
\n
\n
\n {!this.state.isReader ?\n
\n {this.state.readingBlocked ?\n
\n \n
\n : null }\n
\n : null }\n
\n {this.state.peerMessagingDisabled && !this.state.unconfirmed ?\n
: null}\n {this.state.unconfirmed ?\n
\n :\n
}\n {this.state.imagePreview ?\n
: null}\n
\n );\n } else {\n component = (\n \n );\n }\n return component;\n }\n};\n\nexport default injectIntl(MessagesView);\n","import React from 'react';\n\nexport default class MenuContacts extends React.PureComponent {\n render() {\n return (\n \n );\n }\n};\n","import React from 'react';\n\nexport default class MenuStart extends React.PureComponent {\n render() {\n return (\n \n );\n }\n};\n","import React from 'react';\n\nimport LetterTile from './letter-tile.jsx';\nimport MenuCancel from './menu-cancel.jsx';\nimport MenuContacts from './menu-contacts.jsx';\nimport MenuStart from './menu-start.jsx';\n\nexport default class SideNavbar extends React.PureComponent {\n render() {\n return (\n \n
\n \n
\n
{this.props.title}
\n {this.props.state === 'login' ?\n
:\n this.props.state === 'contacts' ?\n
:\n null}\n {this.props.onCancel ?\n
: null}\n
\n );\n }\n};\n","/* ContactsView holds all contacts-related stuff */\nimport React from 'react';\nimport { FormattedHTMLMessage, defineMessages } from 'react-intl';\n\nimport ContactList from '../widgets/contact-list.jsx';\n\nimport { updateFavicon } from '../lib/utils.js';\n\nconst messages = defineMessages({\n archived_contacts_title: {\n id: \"archived_contacts\",\n defaultMessage: \"Archived contacts ({count})\",\n description: \"Label for archived chats\"\n }\n});\n\nexport default class ContactsView extends React.Component {\n constructor(props) {\n super(props);\n\n this.handleAction = this.handleAction.bind(this);\n\n this.state = ContactsView.deriveStateFromProps(props);\n }\n\n static deriveStateFromProps(props) {\n const contacts = [];\n let unreadThreads = 0;\n let archivedCount = 0;\n props.chatList.map((c) => {\n if (c.private && c.private.arch) {\n if (props.archive) {\n contacts.push(c);\n } else {\n archivedCount ++;\n }\n } else if (!props.archive) {\n contacts.push(c);\n unreadThreads += c.unread > 0 ? 1 : 0;\n }\n });\n contacts.sort(function(a, b) {\n return (b.touched || 0) - (a.touched || 0);\n });\n\n if (archivedCount > 0) {\n contacts.push({\n action: 'archive',\n title: messages.archived_contacts_title,\n values: {count: archivedCount}\n });\n }\n\n return {\n contactList: contacts,\n unreadThreads: unreadThreads\n };\n }\n\n componentDidUpdate(prevProps, prevState) {\n if (prevProps.chatList != this.props.chatList || prevProps.archive != this.props.archive) {\n const newState = ContactsView.deriveStateFromProps(this.props);\n this.setState(newState);\n if (newState.unreadThreads != prevState.unreadThreads) {\n updateFavicon(newState.unreadThreads);\n }\n }\n }\n\n handleAction(action_ignored) {\n this.props.onShowArchive();\n }\n\n render() {\n return (\n ¯∖_(ツ)_/¯\"\n description=\"HTML message shown in ContactList when no contacts are found\">{\n (no_contacts) => \n }\n );\n }\n};\n","// Helper functions for storing values in localStorage.\n// By default localStorage can store only strings, not objects or other types.\n\nexport default class LocalStorageUtil {\n // Replace old object with the new one.\n static setObject(key, value) {\n localStorage.setItem(key, JSON.stringify(value));\n }\n\n // Get stored object.\n static getObject(key) {\n const value = localStorage.getItem(key);\n return value && JSON.parse(value);\n }\n\n // Partially or wholly update stored object.\n static updateObject(key, value) {\n const oldVal = this.getObject(key);\n this.setObject(key, Object.assign(oldVal || {}, value));\n }\n\n // Just a wrapper.\n static removeItem(key) {\n localStorage.removeItem(key);\n }\n}\n","// Account registration.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport CheckBox from '../widgets/checkbox.jsx';\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport LocalStorageUtil from '../lib/local-storage.js';\nimport { vcard } from '../lib/utils.js';\n\nimport { MAX_TITLE_LENGTH } from '../config.js';\n\nexport default class CreateAccountView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n login: '',\n password: '',\n email: '',\n fn: '', // full/formatted name\n imageDataUrl: null,\n errorCleared: false,\n saveToken: LocalStorageUtil.getObject('keep-logged-in')\n };\n\n this.handleLoginChange = this.handleLoginChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n this.handleEmailChange = this.handleEmailChange.bind(this);\n this.handleFnChange = this.handleFnChange.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleToggleSaveToken = this.handleToggleSaveToken.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleLoginChange(e) {\n this.setState({login: e.target.value});\n }\n\n handlePasswordChange(password) {\n this.setState({password: password});\n }\n\n handleEmailChange(e) {\n this.setState({email: e.target.value})\n }\n\n handleFnChange(e) {\n this.setState({fn: e.target.value});\n }\n\n handleImageChanged(img) {\n this.setState({imageDataUrl: img});\n }\n\n handleToggleSaveToken() {\n LocalStorageUtil.setObject('keep-logged-in', !this.state.saveToken);\n this.setState({saveToken: !this.state.saveToken});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.setState({errorCleared: false});\n this.props.onCreateAccount(\n this.state.login.trim(),\n this.state.password.trim(),\n vcard(this.state.fn.trim().substring(0, MAX_TITLE_LENGTH), this.state.imageDataUrl),\n {'meth': 'email', 'val': this.state.email});\n }\n\n render() {\n let submitClasses = 'blue';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n\n return (\n \n \n
\n {\n (login_prompt) => \n } \n {\n (password_prompt) => \n } \n
\n
\n
\n \n {\n (full_name_prompt) => \n } \n
\n \n {\n (email_prompt) => \n } \n
\n \n \n {\n (stay_logged_in) => {stay_logged_in} \n } \n
\n \n \n \n \n
\n \n );\n }\n};\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport CheckBox from '../widgets/checkbox.jsx';\nimport InPlaceEdit from '../widgets/in-place-edit.jsx';\nimport PermissionsEditor from '../widgets/permissions-editor.jsx';\nimport TagManager from '../widgets/tag-manager.jsx';\n\nimport { LINK_CONTACT_US, LINK_PRIVACY_POLICY, LINK_TERMS_OF_SERVICE, MAX_TITLE_LENGTH } from '../config.js';\nimport { makeImageUrl } from '../lib/blob-helpers.js';\nimport { arrayEqual, asEmail, asPhone, vcard } from '../lib/utils.js';\n\nexport default class EditAccountView extends React.Component {\n constructor(props) {\n super(props);\n\n const me = this.props.tinode.getMeTopic();\n const defacs = me.getDefaultAccess();\n this.state = {\n fullName: me.public ? me.public.fn : undefined,\n avatar: makeImageUrl(me.public ? me.public.photo : null),\n auth: defacs ? defacs.auth : null,\n anon: defacs ? defacs.anon : null,\n tags: me.tags(),\n credentials: me.getCredentials() || [],\n addCredActive: false,\n addCredInvalid: false,\n newCred: '',\n showPermissionEditorFor: undefined,\n previousOnTags: me.onTagsUpdated\n };\n\n this.tnNewTags = this.tnNewTags.bind(this);\n this.tnCredsUpdated = this.tnCredsUpdated.bind(this);\n this.handleFullNameUpdate = this.handleFullNameUpdate.bind(this);\n this.handlePasswordUpdate = this.handlePasswordUpdate.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleCheckboxClick = this.handleCheckboxClick.bind(this);\n this.handleCredChange = this.handleCredChange.bind(this);\n this.handleCredKeyDown = this.handleCredKeyDown.bind(this);\n this.handleCredEntered = this.handleCredEntered.bind(this);\n this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);\n this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);\n this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);\n this.handleTagsUpdated = this.handleTagsUpdated.bind(this);\n }\n\n componentDidMount() {\n const me = this.props.tinode.getMeTopic();\n me.onCredsUpdated = this.tnCredsUpdated;\n me.onTagsUpdated = this.tnNewTags;\n }\n\n componentWillUnmount() {\n const me = this.props.tinode.getMeTopic();\n me.onTagsUpdated = this.state.previousOnTags;\n me.onCredsUpdated = undefined;\n }\n\n tnNewTags(tags) {\n this.setState({tags: tags});\n }\n\n tnCredsUpdated(creds) {\n this.setState({credentials: creds || []});\n }\n\n handleFullNameUpdate(fn) {\n fn = fn.trim().substring(0, MAX_TITLE_LENGTH);\n if (fn) {\n this.setState({fullName: fn});\n this.props.onUpdateAccount(undefined, vcard(fn, null));\n }\n }\n\n handlePasswordUpdate(pwd) {\n this.setState({password: pwd});\n this.props.onUpdateAccount(pwd);\n }\n\n handleImageChanged(img) {\n this.setState({avatar: img});\n this.props.onUpdateAccount(undefined, vcard(null, img || Tinode.DEL_CHAR));\n }\n\n handleCheckboxClick(what, checked) {\n if (what == 'sound') {\n this.props.onToggleMessageSounds(checked);\n } else if (what == 'alert') {\n this.props.onTogglePushNotifications(checked);\n }\n }\n\n handleCredChange(e) {\n this.setState({newCred: e.target.value, addCredInvalid: false});\n }\n\n handleCredKeyDown(e) {\n if (e.keyCode === 27) {\n // Escape pressed\n this.setState({newCred: '', addCredActive: false});\n } else if (e.keyCode === 13) {\n // Enter pressed\n this.handleCredEntered(e);\n }\n }\n\n handleCredEntered(e) {\n let value = this.state.newCred.trim();\n if (!value) {\n this.setState({addCredActive: false, addCredInvalid: false});\n return;\n }\n\n let val = asPhone(value);\n let method;\n if (val) {\n method = 'tel';\n } else {\n val = asEmail(value);\n if (val) {\n method = 'email';\n }\n }\n if (method) {\n this.props.onCredAdd(method, val);\n this.setState({addCredActive: false, newCred: ''});\n } else {\n this.setState({addCredInvalid: true});\n }\n }\n\n handleLaunchPermissionsEditor(which) {\n this.setState({\n showPermissionEditorFor: which,\n editedPermissions: this.state[which]\n });\n }\n\n handleHidePermissionsEditor() {\n this.setState({showPermissionEditorFor: undefined});\n }\n\n handlePermissionsChanged(perm) {\n let defacs = {};\n defacs[this.state.showPermissionEditorFor] = perm;\n this.props.onUpdateAccount(undefined, undefined, defacs);\n\n let newState = {showPermissionEditorFor: undefined};\n newState[this.state.showPermissionEditorFor] = perm;\n this.setState(newState);\n }\n\n handleTagsUpdated(tags) {\n // Check if tags have actually changed.\n if (arrayEqual(this.state.tags.slice(0), tags.slice(0))) {\n return;\n }\n this.props.onUpdateTags(tags);\n }\n\n render() {\n let credentials = [];\n this.state.credentials.map((cred) => {\n credentials.push();\n });\n\n return (\n {this.state.showPermissionEditorFor ?\n \n :\n \n
\n
\n
\n \n
\n
{\n (full_name_placeholder) => \n }
\n
\n \n
\n
\n {\n (password_unchanged) => \n } \n
\n
\n
\n
\n
\n
\n \n \n \n \n
\n
\n \n {this.props.desktopAlertsEnabled ?\n \n :\n \n }\n \n \n
\n
\n
{\n (title_tag_manager) => \n } \n
\n
\n
\n \n \n \n
\n
\n {credentials}\n {this.state.addCredActive ?\n
\n : null}\n
\n
\n
\n
\n
\n
\n \n {this.props.myUserId} \n
\n
\n \n \n \n
\n
\n
Auth: {this.state.auth}
\n
Anon: {this.state.anon}
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n
\n
\n
\n
\n
\n
\n } \n );\n }\n};\n","// Login form.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport CheckBox from '../widgets/checkbox.jsx';\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport LocalStorageUtil from '../lib/local-storage.js';\n\nexport default class LoginView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n login: props.login,\n password: '',\n hostName: props.serverAddress,\n saveToken: LocalStorageUtil.getObject('keep-logged-in')\n };\n this.handleLoginChange = this.handleLoginChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n this.handleToggleSaveToken = this.handleToggleSaveToken.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleLoginChange(e) {\n this.setState({login: e.target.value});\n }\n\n handlePasswordChange(e) {\n this.setState({password: e.target.value});\n }\n\n handleToggleSaveToken() {\n LocalStorageUtil.setObject('keep-logged-in', !this.state.saveToken);\n this.setState({saveToken: !this.state.saveToken});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.props.onLogin(this.state.login.trim(), this.state.password.trim());\n }\n\n render() {\n var submitClasses = 'blue';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n\n return (\n \n \n {\n (login_prompt) => \n }\n \n \n {\n (password_prompt) => \n }\n \n \n \n \n \n \n
\n \n );\n }\n};\n/* END Login */\n","import React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nconst messages = defineMessages({\n invalid_id: {\n id: 'error_invalid_id',\n defaultMessage: 'Invalid ID',\n description: 'Error message'\n }\n});\n\nclass NewTopicById extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n groupId: '',\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleChange(e) {\n this.setState({groupId: e.target.value});\n }\n\n handleKeyPress(e) {\n if (e.key === 'Enter') {\n this.handleSubmit(e);\n }\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.groupId) {\n var name = this.state.groupId.trim();\n if (name.length > 3 && (name.substr(0, 3) == 'usr' || name.substr(0, 3) == 'grp')) {\n this.props.onSubmit(name);\n } else {\n this.props.onError(this.props.intl.formatMessage(messages.invalid_id), 'err');\n }\n }\n }\n\n render() {\n return (\n \n
\n {\n (prompt) => \n } \n
\n
\n \n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(NewTopicById);\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from './avatar-upload.jsx';\nimport TagManager from './tag-manager.jsx';\n\nimport { MAX_TITLE_LENGTH } from '../config.js';\n\nexport default class NewTopicGroup extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n fn: '', // full/formatted name\n private: '',\n imageDataUrl: null,\n tags: []\n };\n\n this.handleFnChange = this.handleFnChange.bind(this);\n this.handlePrivateChange = this.handlePrivateChange.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleTagsChanged = this.handleTagsChanged.bind(this);\n this.handleTagsChanged = this.handleTagsChanged.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleFnChange(e) {\n this.setState({fn: e.target.value});\n }\n\n handlePrivateChange(e) {\n this.setState({private: e.target.value});\n }\n\n handleImageChanged(img) {\n this.setState({imageDataUrl: img});\n }\n\n handleTagsChanged(tags) {\n this.setState({tags: tags});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n\n const fn = this.state.fn.trim().substring(0, MAX_TITLE_LENGTH);\n const comment = this.state.private.trim().substring(0, MAX_TITLE_LENGTH);\n if (fn) {\n this.props.onSubmit(fn, this.state.imageDataUrl, comment, this.state.tags);\n }\n }\n\n render() {\n var submitClasses = 'blue';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n return (\n \n \n {\n (title) => \n } \n \n \n \n \n
\n \n );\n }\n};\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nexport default class SearchContacts extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n edited: false,\n search: ''\n };\n\n this.handleSearchChange = this.handleSearchChange.bind(this);\n this.handleSearch = this.handleSearch.bind(this);\n this.handleClear = this.handleClear.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n }\n\n componentWillUnmount() {\n if (this.state.edited) {\n this.setState({search: '', edited: false});\n this.props.onSearchContacts(Tinode.DEL_CHAR);\n }\n }\n\n handleSearchChange(e) {\n this.setState({search: e.target.value});\n }\n\n handleSearch(e) {\n e.preventDefault();\n var query = this.state.search.trim();\n this.setState({edited: (query.length > 0)});\n this.props.onSearchContacts(query.length > 0 ? query : Tinode.DEL_CHAR);\n }\n\n handleClear(e) {\n e.preventDefault();\n if (this.state.edited) {\n this.props.onSearchContacts(Tinode.DEL_CHAR);\n }\n this.setState({search: '', edited: false});\n }\n\n handleKeyDown(e) {\n if (e.key === 'Enter') {\n this.handleSearch(e);\n } else if (e.key === 'Escape') {\n this.handleClear();\n }\n }\n\n render() {\n return (\n \n
\n
search \n
{\n (search_placeholder) => \n } \n
\n close \n \n
\n
\n );\n }\n};\n","// Create new topic and invite users or send an invite.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport ContactList from '../widgets/contact-list.jsx';\nimport NewTopicById from '../widgets/new-topic-by-id.jsx';\nimport NewTopicGroup from '../widgets/new-topic-group.jsx';\nimport SearchContacts from '../widgets/search-contacts.jsx';\n\nimport HashNavigation from '../lib/navigation.js';\nimport { vcard } from '../lib/utils.js';\n\nconst messages = defineMessages({\n search_for_contacts: {\n id: \"search_for_contacts\",\n defaultMessage: \"Use search to find contacts\",\n description: \"Text shown in contacts view when user entered no search query.\"\n },\n search_no_results: {\n id: \"search_no_results\",\n defaultMessage: \"Search returned no results\",\n description: \"Text shown in contacts view when query returned no results.\"\n }\n});\n\nclass NewTopicView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n tabSelected: 'p2p',\n searchQuery: null\n };\n\n this.handleTabClick = this.handleTabClick.bind(this);\n this.handleSearchContacts = this.handleSearchContacts.bind(this);\n this.handleContactSelected = this.handleContactSelected.bind(this);\n this.handleNewGroupSubmit = this.handleNewGroupSubmit.bind(this);\n this.handleGroupByID = this.handleGroupByID.bind(this);\n }\n\n componentDidMount() {\n this.props.onInitFind();\n }\n\n handleTabClick(e) {\n e.preventDefault();\n HashNavigation.navigateTo(HashNavigation.addUrlParam(window.location.hash, 'tab', e.currentTarget.dataset.id));\n this.setState({tabSelected: e.currentTarget.dataset.id});\n }\n\n handleSearchContacts(query) {\n this.props.onSearchContacts(query);\n this.setState({searchQuery: Tinode.isNullValue(query) ? null : query});\n }\n\n handleContactSelected(sel) {\n if (this.state.tabSelected === 'p2p') {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(sel, undefined);\n }\n }\n\n handleNewGroupSubmit(name, dataUrl, priv, tags) {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(undefined, vcard(name, dataUrl), priv, tags);\n }\n\n handleGroupByID(topicName) {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(topicName);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const no_contacts_placeholder = formatMessage(this.state.searchQuery ?\n messages.search_no_results : messages.search_for_contacts);\n return (\n \n
\n {this.state.tabSelected === 'grp' ?\n
:\n this.state.tabSelected === 'byid' ?\n
:\n
\n \n \n
}\n
\n );\n }\n};\n\nexport default injectIntl(NewTopicView);\n","// A password reset form.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport HashNavigation from '../lib/navigation.js';\n\nexport default class PasswordResetView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n email: '',\n password: ''\n };\n\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleEmailChange = this.handleEmailChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n }\n\n componentDidMount() {\n let parsed = HashNavigation.parseUrlHash(window.location.hash);\n this.setState({token: parsed.params.token, scheme: parsed.params.scheme});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.token) {\n this.props.onReset(this.state.scheme, this.state.password.trim(), this.state.token);\n } else {\n this.props.onRequest('email', this.state.email.trim());\n }\n }\n\n handleEmailChange(e) {\n this.setState({email: e.target.value});\n }\n\n handlePasswordChange(e) {\n this.setState({password: e.target.value});\n }\n\n render() {\n let reset = (this.state.token && this.state.scheme);\n return (\n \n {reset ?\n {\n (placeholder) => \n } \n :\n <>\n \n \n \n {\n (placeholder) => \n } \n >\n }\n \n {reset ?\n :\n \n } \n
\n \n );\n }\n}\n","import React from 'react';\n\nimport { KNOWN_HOSTS } from '../config.js';\n\n/* Combobox for selecting host name */\n\nexport default class HostSelector extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n hostName: props.serverAddress,\n changed: false\n };\n\n this.handleHostNameChange = this.handleHostNameChange.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n }\n\n handleHostNameChange(e) {\n this.setState({hostName: e.target.value, changed: true});\n }\n\n handleEditingFinished() {\n if (this.state.changed) {\n this.setState({changed: false});\n this.props.onServerAddressChange(this.state.hostName.trim());\n }\n }\n\n render() {\n var hostOptions = [];\n for (let key in KNOWN_HOSTS) {\n let item = KNOWN_HOSTS[key];\n hostOptions.push(\n \n );\n }\n return (\n \n \n \n {hostOptions}\n \n
\n );\n }\n}\n","// Tinode config panel.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport HostSelector from '../widgets/host-selector.jsx';\n\nexport default class SettingsView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n transport: props.transport || 'def',\n serverAddress: props.serverAddress,\n };\n\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleTransportSelected = this.handleTransportSelected.bind(this);\n this.handleServerAddressChange = this.handleServerAddressChange.bind(this);\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.props.onUpdate({\n transport: this.state.transport,\n serverAddress: this.state.serverAddress\n });\n }\n\n handleTransportSelected(e) {\n this.setState({transport: e.currentTarget.value});\n }\n\n handleServerAddressChange(name) {\n this.setState({serverAddress: name});\n }\n\n render() {\n const names = {def: \"default\", ws: \"websocket\", lp: \"long polling\"};\n var transportOptions = [];\n var instance = this;\n ['def', 'ws', 'lp'].map(function(item) {\n var id = 'transport-' + item;\n var name = names[item];\n transportOptions.push(\n \n \n {name} \n \n );\n });\n\n return (\n \n \n \n \n \n
\n \n \n \n \n \n
\n \n \n \n \n \n
\n \n );\n }\n};\n","// ValidationView: panel for confirming credentials, like email or phone.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nconst messages = defineMessages({\n phone: {\n id: 'phone_dative',\n defaultMessage: 'phone',\n description: \"Dative case of 'phone', i.e. 'phone' in 'by phone'\",\n },\n email: {\n id: 'email_dative',\n defaultMessage: 'email',\n description: \"Dative case of 'email', i.e. 'email' in 'by email'\",\n }\n});\n\nclass ValidationView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n code: props.credCode || ''\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n handleChange(e) {\n this.setState({code: e.target.value});\n }\n\n handleKeyPress(e) {\n if (e.key === 'Enter') {\n this.handleSubmit(e);\n } else if (e.key == 'Escape') {\n this.handleCancel(e);\n }\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.code && this.state.code.trim()) {\n this.props.onSubmit(this.props.credMethod, this.state.code.trim());\n }\n }\n\n handleCancel(e) {\n e.preventDefault();\n this.props.onCancel();\n }\n\n\n render() {\n const { formatMessage } = this.props.intl;\n const methods = {'email': formatMessage(messages.email), 'tel': formatMessage(messages.phone)};\n let method = methods[this.props.credMethod] || this.props.credMethod;\n return (\n \n
\n \n \n \n
\n
\n {\n (numbers_only) => \n } \n
\n
\n \n \n \n \n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(ValidationView);\n","import React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport LoadSpinner from '../widgets/load-spinner.jsx';\nimport SideNavbar from '../widgets/side-navbar.jsx';\n\nimport ContactsView from './contacts-view.jsx';\nimport CreateAccountView from './create-account-view.jsx';\nimport EditAccountView from './edit-account-view.jsx';\nimport LoginView from './login-view.jsx';\nimport NewTopicView from './new-topic-view.jsx';\nimport PasswordResetView from './password-reset-view.jsx';\nimport SettingsView from './settings-view.jsx';\nimport ValidationView from './validation-view.jsx';\n\n// Panel titles for translation.\nconst messages = defineMessages({\n 'login': {\n id: 'sidepanel_title_login',\n description: 'Sidepanel title for LoginView.',\n defaultMessage: 'Sign In'\n },\n 'register': {\n id: 'sidepanel_title_register',\n description: 'Sidepanel title for CreateAccountView.',\n defaultMessage: 'Create Account'\n },\n 'settings': {\n id: 'sidepanel_title_settings',\n description: 'Sidepanel title for SettingsView.',\n defaultMessage: 'Settings'\n },\n 'edit': {\n id: 'sidepanel_title_edit_account',\n description: 'Sidepanel title for EditAccountView.',\n defaultMessage: 'Edit Account'\n },\n 'newtpk': {\n id: 'sidepanel_title_newtpk',\n description: 'Sidepanel title for NewTopicView.',\n defaultMessage: 'Start New Chat'\n },\n 'cred': {\n id: 'sidepanel_title_cred',\n description: 'Sidepanel title for ValidationView.',\n defaultMessage: 'Confirm Credentials'\n },\n 'reset': {\n id: 'sidepanel_title_reset',\n description: 'Sidepanel title for PasswordResetView.',\n defaultMessage: 'Reset Password'\n },\n 'archive': {\n id: 'sidepanel_title_archive',\n description: 'Sidepanel title for ContactsView-Archive.',\n defaultMessage: 'Archived Chats'\n }\n});\n\nclass SidepanelView extends React.Component {\n constructor(props) {\n super(props);\n\n this.handleLoginRequested = this.handleLoginRequested.bind(this);\n }\n\n handleLoginRequested(login, password) {\n this.props.onLoginRequest(login, password);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const view = this.props.state || (this.props.myUserId ? 'contacts' : 'login');\n\n let title, avatar;\n if (view == 'contacts') {\n title = this.props.title;\n avatar = this.props.avatar ? this.props.avatar : true;\n } else {\n title = formatMessage(messages[view]);\n avatar = false;\n }\n\n let onCancel;\n if (['login', 'contacts'].indexOf(view) == -1) {\n onCancel = this.props.onCancel;\n }\n\n return (\n \n
\n\n
\n\n
\n\n {view === 'login' ?\n
:\n\n view === 'register' ?\n
:\n\n view === 'settings' ?\n
:\n\n view === 'edit' ?\n
:\n\n (view === 'contacts' || view == 'archive') ?\n
:\n\n view === 'newtpk' ?\n
:\n\n view === 'cred' ?\n
:\n\n view === 'reset' ?\n
:\n null}\n
\n );\n }\n};\n\nexport default injectIntl(SidepanelView);\n","import { KNOWN_HOSTS, DEFAULT_HOST } from '../config.js';\n\n// Detect server address from the URL\nexport function detectServerAddress() {\n let host = DEFAULT_HOST;\n if (typeof window.location == 'object') {\n if (window.location.protocol == 'file:' || window.location.hostname == 'localhost') {\n host = KNOWN_HOSTS.local;\n } else if (window.location.hostname) {\n host = window.location.hostname + (window.location.port ? ':' + window.location.port : '');\n }\n }\n return host;\n}\n\n// Detect if the page is served over HTTPS.\nexport function isSecureConnection() {\n if (typeof window.location == 'object') {\n return window.location.protocol == 'https:';\n }\n return false;\n}\n\nexport function isLocalHost() {\n if (typeof window.location == 'object') {\n return window.location.hostname == 'localhost';\n }\n return false;\n}\n","// The top-level class to hold all functionality together.\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport * as firebase from 'firebase/app';\nimport 'firebase/messaging';\n\nimport Tinode from 'tinode-sdk';\n\nimport Alert from '../widgets/alert.jsx';\nimport ContextMenu from '../widgets/context-menu.jsx';\n\nimport InfoView from './info-view.jsx';\nimport MessagesView from './messages-view.jsx';\nimport SidepanelView from './sidepanel-view.jsx';\n\nimport { API_KEY, APP_NAME, DEFAULT_P2P_ACCESS_MODE, LOGGING_ENABLED, MEDIA_BREAKPOINT,\n READ_DELAY, RECEIVED_DELAY } from '../config.js';\nimport { base64ReEncode, makeImageUrl } from '../lib/blob-helpers.js';\nimport { detectServerAddress, isLocalHost, isSecureConnection } from '../lib/host-name.js';\nimport LocalStorageUtil from '../lib/local-storage.js';\nimport HashNavigation from '../lib/navigation.js';\nimport { secondsToTime } from '../lib/strformat.js'\nimport { updateFavicon } from '../lib/utils.js';\n\n// Sound to play on message received.\nconst POP_SOUND = new Audio('audio/msg.mp3');\n\nconst messages = defineMessages({\n update_available: {\n id: 'update_available',\n defaultMessage: 'Update available. Reload .',\n description: 'Message shown when an app update is available.'\n },\n reconnect_countdown: {\n id: 'reconnect_countdown',\n defaultMessage: 'Disconnected. Reconnecting in {seconds}…',\n description: 'Message shown when an app update is available.'\n },\n reconnect_now: {\n id: 'reconnect_now',\n defaultMessage: 'Try now',\n description: 'Prompt for reconnecting now'\n }\n});\n\nclass TinodeWeb extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = this.getBlankState();\n\n this.handleResize = this.handleResize.bind(this);\n this.handleHashRoute = this.handleHashRoute.bind(this);\n this.handleOnline = this.handleOnline.bind(this);\n this.checkForAppUpdate = this.checkForAppUpdate.bind(this);\n this.handleAppVisibility = this.handleAppVisibility.bind(this);\n this.handleReadTimer = this.handleReadTimer.bind(this);\n this.handleVisibilityEvent = this.handleVisibilityEvent.bind(this);\n this.handleError = this.handleError.bind(this);\n this.handleLoginRequest = this.handleLoginRequest.bind(this);\n this.handleConnected = this.handleConnected.bind(this);\n this.handleAutoreconnectIteration = this.handleAutoreconnectIteration.bind(this);\n this.doLogin = this.doLogin.bind(this);\n this.handleCredentialsRequest = this.handleCredentialsRequest.bind(this);\n this.handleLoginSuccessful = this.handleLoginSuccessful.bind(this);\n this.handleDisconnect = this.handleDisconnect.bind(this);\n this.tnMeMetaDesc = this.tnMeMetaDesc.bind(this);\n this.tnMeContactUpdate = this.tnMeContactUpdate.bind(this);\n this.tnMeSubsUpdated = this.tnMeSubsUpdated.bind(this);\n this.resetContactList = this.resetContactList.bind(this);\n this.tnData = this.tnData.bind(this);\n this.tnInitFind = this.tnInitFind.bind(this);\n this.tnFndSubsUpdated = this.tnFndSubsUpdated.bind(this);\n this.handleSearchContacts = this.handleSearchContacts.bind(this);\n this.handleTopicSelected = this.handleTopicSelected.bind(this);\n this.handleHideMessagesView = this.handleHideMessagesView.bind(this);\n this.handleSendMessage = this.handleSendMessage.bind(this);\n this.handleNewChatInvitation = this.handleNewChatInvitation.bind(this);\n this.handleNewAccount = this.handleNewAccount.bind(this);\n this.handleNewAccountRequest = this.handleNewAccountRequest.bind(this);\n this.handleUpdateAccountRequest = this.handleUpdateAccountRequest.bind(this);\n this.handleUpdateAccountTagsRequest = this.handleUpdateAccountTagsRequest.bind(this);\n this.handleSettings = this.handleSettings.bind(this);\n this.handleGlobalSettings = this.handleGlobalSettings.bind(this);\n this.handleShowArchive = this.handleShowArchive.bind(this);\n this.handleToggleMessageSounds = this.handleToggleMessageSounds.bind(this);\n this.handleCredAdd = this.handleCredAdd.bind(this);\n this.handleCredDelete = this.handleCredDelete.bind(this);\n this.handleCredConfirm = this.handleCredConfirm.bind(this);\n this.initDesktopAlerts = this.initDesktopAlerts.bind(this);\n this.togglePushToken = this.togglePushToken.bind(this);\n this.requestPushToken = this.requestPushToken.bind(this);\n this.handleSidepanelCancel = this.handleSidepanelCancel.bind(this);\n this.handleNewTopic = this.handleNewTopic.bind(this);\n this.handleNewTopicRequest = this.handleNewTopicRequest.bind(this);\n this.handleNewTopicCreated = this.handleNewTopicCreated.bind(this);\n this.handleTopicUpdateRequest = this.handleTopicUpdateRequest.bind(this);\n this.handleChangePermissions = this.handleChangePermissions.bind(this);\n this.handleTagsUpdated = this.handleTagsUpdated.bind(this);\n this.handleLogout = this.handleLogout.bind(this);\n this.handleDeleteMessagesRequest = this.handleDeleteMessagesRequest.bind(this);\n this.handleLeaveUnsubRequest = this.handleLeaveUnsubRequest.bind(this);\n this.handleBlockTopicRequest = this.handleBlockTopicRequest.bind(this);\n this.handleReportTopic = this.handleReportTopic.bind(this);\n this.handleShowContextMenu = this.handleShowContextMenu.bind(this);\n this.defaultTopicContextMenu = this.defaultTopicContextMenu.bind(this);\n this.handleHideContextMenu = this.handleHideContextMenu.bind(this);\n this.handleShowAlert = this.handleShowAlert.bind(this);\n this.handleShowInfoView = this.handleShowInfoView.bind(this);\n this.handleHideInfoView = this.handleHideInfoView.bind(this);\n this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);\n this.handleValidateCredentialsRequest = this.handleValidateCredentialsRequest.bind(this);\n this.handlePasswordResetRequest = this.handlePasswordResetRequest.bind(this);\n this.handleResetPassword = this.handleResetPassword.bind(this);\n this.handleContextMenuAction = this.handleContextMenuAction.bind(this);\n }\n\n getBlankState() {\n const settings = LocalStorageUtil.getObject('settings') || {};\n\n return {\n connected: false,\n // Connected and subscribed to 'me'\n ready: false,\n transport: settings.transport || null,\n serverAddress: settings.serverAddress || detectServerAddress(),\n serverVersion: \"no connection\",\n // \"On\" is the default, so saving the \"off\" state.\n messageSounds: !settings.messageSoundsOff,\n desktopAlerts: settings.desktopAlerts,\n desktopAlertsEnabled: (isSecureConnection() || isLocalHost()) &&\n (typeof firebase != 'undefined') && (typeof navigator != 'undefined') &&\n (typeof FIREBASE_INIT != 'undefined'),\n firebaseToken: LocalStorageUtil.getObject('firebase-token'),\n\n errorText: '',\n errorLevel: null,\n errorAction: undefined,\n errorActionText: null,\n\n sidePanelSelected: 'login',\n sidePanelTitle: null,\n sidePanelAvatar: null,\n loadSpinnerVisible: false,\n\n login: '',\n password: '',\n myUserId: null,\n liveConnection: navigator.onLine,\n topicSelected: '',\n topicSelectedOnline: false,\n topicSelectedAcs: null,\n newTopicParams: null,\n loginDisabled: false,\n displayMobile: (window.innerWidth <= MEDIA_BREAKPOINT),\n showInfoPanel: false,\n mobilePanel: 'sidepanel',\n\n contextMenuVisible: false,\n contextMenuBounds: null,\n contextMenuClickAt: null,\n contextMenuParams: null,\n contextMenuItems: [],\n\n // Modal alert dialog.\n alertVisible: false,\n alertParams: {},\n\n // Chats as shown in the ContactsView\n chatList: [],\n // Contacts returned by a search query.\n searchResults: [],\n // Merged results of a search query and p2p chats.\n searchableContacts: [],\n credMethod: undefined,\n credCode: undefined\n };\n }\n\n componentDidMount() {\n window.addEventListener('resize', this.handleResize);\n window.addEventListener('online', (e) => { this.handleOnline(true); });\n window.addEventListener('offline', (e) => { this.handleOnline(false); });\n window.addEventListener('hashchange', this.handleHashRoute);\n // Window/tab visible or invisible for pausing timers.\n document.addEventListener('visibilitychange', this.handleVisibilityEvent);\n\n this.setState({\n viewportWidth: document.documentElement.clientWidth,\n viewportHeight: document.documentElement.clientHeight\n });\n\n this.tinode = TinodeWeb.tnSetup(this.state.serverAddress, this.state.transport);\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n this.tinode.onAutoreconnectIteration = this.handleAutoreconnectIteration;\n\n // Initialize desktop alerts.\n if (this.state.desktopAlertsEnabled) {\n try {\n this.fbPush = firebase.initializeApp(FIREBASE_INIT, APP_NAME).messaging();\n this.fbPush.usePublicVapidKey(FIREBASE_INIT.messagingVapidKey);\n navigator.serviceWorker.register('/service-worker.js').then((reg) => {\n this.checkForAppUpdate(reg);\n this.fbPush.useServiceWorker(reg);\n this.initDesktopAlerts();\n if (this.state.desktopAlerts) {\n if (!this.state.firebaseToken) {\n this.togglePushToken(true);\n } else {\n this.tinode.setDeviceToken(this.state.firebaseToken, true);\n }\n }\n }).catch((err) => {\n // registration failed :(\n console.log(\"Failed to register service worker:\", err);\n });\n } catch (err) {\n this.handleError(\"Failed to initialize push notifications\", 'err');\n console.log(\"Failed to initialize push notifications\", err);\n this.setState({desktopAlertsEnabled: false});\n }\n }\n\n const token = LocalStorageUtil.getObject('keep-logged-in') ?\n LocalStorageUtil.getObject('auth-token') : undefined;\n\n const parsedNav = HashNavigation.parseUrlHash(window.location.hash);\n if (token) {\n // When reading from storage, date is returned as string.\n token.expires = new Date(token.expires);\n this.tinode.setAuthToken(token);\n this.tinode.connect().catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n delete parsedNav.params.info;\n delete parsedNav.params.tab;\n parsedNav.path[0] = '';\n HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsedNav.path, parsedNav.params));\n } else if (!parsedNav.params.token) {\n HashNavigation.navigateTo('');\n }\n\n this.readTimer = null;\n this.readTimerCallback = null;\n\n this.handleHashRoute();\n }\n\n componentWillUnmount() {\n window.removeEventListener('resize', this.handleResize);\n window.removeEventListener('hashchange', this.handleHashRoute);\n document.removeEventListener('visibilitychange', this.handleVisibilityEvent);\n }\n\n // Setup transport (usually websocket) and server address. This will terminate connection with the server.\n static tnSetup(serverAddress, transport) {\n let tinode = new Tinode(APP_NAME, serverAddress, API_KEY, transport, isSecureConnection());\n tinode.enableLogging(LOGGING_ENABLED, true);\n return tinode;\n }\n\n handleResize() {\n var mobile = document.documentElement.clientWidth <= MEDIA_BREAKPOINT;\n this.setState({\n viewportWidth: document.documentElement.clientWidth,\n viewportHeight: document.documentElement.clientHeight\n });\n if (this.state.displayMobile != mobile) {\n this.setState({displayMobile: mobile});\n }\n }\n\n // Check if a newer version of TinodeWeb app is available at the server.\n checkForAppUpdate(reg) {\n const {formatHTMLMessage} = this.props.intl;\n reg.onupdatefound = () => {\n const installingWorker = reg.installing;\n installingWorker.onstatechange = () => {\n if (installingWorker.state == 'installed' && navigator.serviceWorker.controller) {\n this.handleError(formatHTMLMessage(messages.update_available), 'info');\n }\n }\n }\n }\n\n // Handle for hashchange event: display appropriate panels.\n handleHashRoute() {\n var hash = HashNavigation.parseUrlHash(window.location.hash);\n if (hash.path && hash.path.length > 0) {\n // Left-side panel selector.\n if (['register','settings','edit','cred','reset','newtpk','archive','contacts',''].includes(hash.path[0])) {\n this.setState({sidePanelSelected: hash.path[0]});\n } else {\n console.log(\"Unknown sidepanel view\", hash.path[0]);\n }\n\n // Topic for MessagesView selector.\n if (hash.path.length > 1 && hash.path[1] != this.state.topicSelected) {\n this.setState({\n topicSelected: Tinode.topicType(hash.path[1]) ? hash.path[1] : null\n });\n }\n } else {\n // Empty hashpath\n this.setState({sidePanelSelected: ''});\n }\n\n // Save validation credentials, if available.\n if (hash.params.method) {\n this.setState({ credMethod: hash.params.method });\n }\n if (hash.params.code) {\n this.setState({ credCode: hash.params.code });\n }\n\n // Additional parameters of panels.\n this.setState({\n showInfoPanel: hash.params.info,\n newTopicTabSelected: hash.params.tab\n });\n }\n\n handleOnline(online) {\n var newState = {liveConnection: online};\n if (online) {\n this.handleError('', null);\n } else {\n this.handleError(\"No connection\", 'warn');\n }\n this.setState({liveConnection: online});\n }\n\n // Handling read notifications here to be able to pause\n // them then the window/tab is not visible.\n handleAppVisibility(visible, callback) {\n clearTimeout(this.readTimer);\n this.readTimerCallback = callback;\n if (visible && callback) {\n this.readTimer = setTimeout(callback, READ_DELAY);\n } else {\n this.readTimer = null;\n }\n }\n\n handleReadTimer(callback) {\n this.handleAppVisibility(!document.hidden, callback);\n }\n\n handleVisibilityEvent() {\n this.handleAppVisibility(!document.hidden, this.readTimerCallback);\n }\n\n handleError(err, level, action, actionText) {\n this.setState({errorText: err, errorLevel: level, errorAction: action, errorActionText: actionText});\n }\n\n // User clicked Login button in the side panel.\n handleLoginRequest(login, password) {\n this.setState({\n loginDisabled: true,\n login: login,\n password: password,\n loadSpinnerVisible: true\n });\n this.handleError('', null);\n\n if (this.tinode.isConnected()) {\n this.doLogin(login, password, {meth: this.state.credMethod, resp: this.state.credCode});\n } else {\n this.tinode.connect().catch((err) => {\n // Socket error\n this.setState({loginDisabled: false});\n this.handleError(err.message, 'err');\n });\n }\n }\n\n // Connection succeeded.\n handleConnected() {\n var params = this.tinode.getServerInfo();\n this.setState({\n serverVersion: params.ver + ' ' + (params.build ? params.build : 'none') + '; '\n });\n this.doLogin(this.state.login, this.state.password, {meth: this.state.credMethod, resp: this.state.credCode});\n }\n\n // Called for each auto-reconnect iteration.\n handleAutoreconnectIteration(sec, prom) {\n clearInterval(this.reconnectCountdown);\n\n if (sec < 0) {\n // Clear error\n this.handleError();\n return;\n }\n\n if (prom) {\n // Reconnecting now\n prom.then(() => {\n // Reconnected: clear error\n this.handleError();\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n return;\n }\n\n const {formatHTMLMessage} = this.props.intl;\n let count = sec / 1000;\n count = count | count;\n this.reconnectCountdown = setInterval(() => {\n const timeLeft = (count > 99) ? secondsToTime(count) : count;\n this.handleError(\n formatHTMLMessage(messages.reconnect_countdown, {seconds: timeLeft}),\n 'warn',\n () => {\n clearInterval(this.reconnectCountdown);\n this.tinode.reconnect();\n },\n formatHTMLMessage(messages.reconnect_now)\n );\n count -= 1;\n }, 1000);\n }\n\n // Connection lost\n handleDisconnect(err) {\n this.setState({\n connected: false,\n ready: false,\n topicSelectedOnline: false,\n errorText: err && err.message ? err.message : \"Disconnected\",\n errorLevel: err && err.message ? 'err' : 'warn',\n loginDisabled: false,\n contextMenuVisible: false,\n serverVersion: \"no connection\"\n });\n }\n\n doLogin(login, password, cred) {\n if (this.tinode.isAuthenticated()) {\n // Already logged in. Go to default screen.\n HashNavigation.navigateTo('');\n return;\n }\n // Sanitize and package credentail.\n cred = Tinode.credential(cred);\n // Try to login with login/password. If they are not available, try token. If no token, ask for login/password.\n let promise = null;\n let token = this.tinode.getAuthToken();\n if (login && password) {\n this.setState({password: null});\n promise = this.tinode.loginBasic(login, password, cred);\n } else if (token) {\n promise = this.tinode.loginToken(token.token, cred);\n }\n\n if (promise) {\n promise.then((ctrl) => {\n if (ctrl.code >= 300 && ctrl.text === 'validate credentials') {\n this.setState({loadSpinnerVisible: false});\n if (cred) {\n this.handleError(\"Code does not match\", 'warn');\n }\n this.handleCredentialsRequest(ctrl.params);\n } else {\n this.handleLoginSuccessful();\n }\n }).catch((err) => {\n // Login failed, report error.\n this.setState({\n loginDisabled: false,\n credMethod: undefined,\n credCode: undefined,\n loadSpinnerVisible: false\n });\n this.handleError(err.message, 'err');\n localStorage.removeItem('auth-token');\n HashNavigation.navigateTo('');\n });\n } else {\n // No login credentials provided.\n // Make sure we are on the login page.\n HashNavigation.navigateTo('');\n this.setState({loginDisabled: false});\n }\n }\n\n handleCredentialsRequest(params) {\n var parsed = HashNavigation.parseUrlHash(window.location.hash);\n parsed.path[0] = 'cred';\n parsed.params['method'] = params.cred[0];\n HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));\n }\n\n handleLoginSuccessful() {\n this.handleError('', null);\n\n // Refresh authentication token.\n if (LocalStorageUtil.getObject('keep-logged-in')) {\n LocalStorageUtil.setObject('auth-token', this.tinode.getAuthToken());\n }\n // Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.\n var me = this.tinode.getMeTopic();\n me.onMetaDesc = this.tnMeMetaDesc;\n me.onContactUpdate = this.tnMeContactUpdate;\n me.onSubsUpdated = this.tnMeSubsUpdated;\n this.setState({\n connected: true,\n credMethod: undefined,\n credCode: undefined,\n myUserId: this.tinode.getCurrentUserID()\n });\n // Subscribe, fetch topic desc, the list of subscriptions. Messages are not fetched.\n me.subscribe(\n me.startMetaQuery().\n withLaterSub().\n withDesc().\n withTags().\n withCred().\n build()\n ).catch((err) => {\n this.tinode.disconnect();\n localStorage.removeItem('auth-token');\n this.handleError(err.message, 'err');\n HashNavigation.navigateTo('');\n }).finally(() => {\n this.setState({loadSpinnerVisible: false});\n });\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'contacts'));\n }\n\n tnMeMetaDesc(desc) {\n if (desc && desc.public) {\n this.setState({\n sidePanelTitle: desc.public.fn,\n sidePanelAvatar: makeImageUrl(desc.public.photo)\n });\n }\n }\n\n // Reactions to updates to the contact list.\n tnMeContactUpdate(what, cont) {\n if (what == 'on' || what == 'off') {\n this.resetContactList();\n if (this.state.topicSelected == cont.topic) {\n this.setState({topicSelectedOnline: (what == 'on')});\n }\n } else if (what == 'read') {\n this.resetContactList();\n } else if (what == 'msg') {\n // Check if the topic is archived. If so, don't play a sound.\n const topic = this.tinode.getTopic(cont.topic);\n const archived = topic && topic.isArchived();\n\n // New message received. Maybe the message is from the current user, then unread is 0.\n if (cont.unread > 0 && this.state.messageSounds && !archived) {\n // Skip update if the topic is currently open, otherwise the badge will annoyingly flash.\n if (document.hidden || this.state.topicSelected != cont.topic) {\n POP_SOUND.play();\n }\n }\n // Reorder contact list to use possibly updated 'touched'.\n this.resetContactList();\n } else if (what == 'recv') {\n // Explicitly ignoring \"recv\" -- it causes no visible updates to contact list.\n } else if (what == 'gone' || what == 'unsub') {\n // Topic deleted or user unsubscribed. Remove topic from view.\n // If the currently selected topic is gone, clear the selection.\n if (this.state.topicSelected == cont.topic) {\n this.handleTopicSelected(null);\n }\n // Redraw without the deleted topic.\n this.resetContactList();\n } else if (what == 'acs') {\n // Permissions changed. If it's for the currently selected topic,\n // update the views.\n if (this.state.topicSelected == cont.topic) {\n this.setState({topicSelectedAcs: cont.acs});\n }\n } else if (what == 'del') {\n // TODO: messages deleted (hard or soft) -- update pill counter.\n } else if (what == 'upd') {\n // upd - handled by the SDK. Explicitly ignoring here.\n } else {\n // TODO(gene): handle other types of notifications:\n // * ua -- user agent changes (maybe display a pictogram for mobile/desktop).\n console.log(\"Unsupported (yet) presence update:\" + what + \" in: \" + cont.topic);\n }\n }\n\n tnMeSubsUpdated(unused) {\n this.resetContactList();\n }\n\n // Merge search results and contact list to create a single flat\n // list of kown contacts for GroupManager to use.\n static prepareSearchableContacts(chatList, foundContacts) {\n let merged = {};\n\n // For chatList topics merge only p2p topics and convert them to the\n // same format as foundContacts.\n for (const c of chatList) {\n if (Tinode.topicType(c.topic) == 'p2p') {\n merged[c.topic] = {\n user: c.topic,\n updated: c.updated,\n public: c.public,\n private: c.private,\n acs: c.acs\n };\n }\n }\n\n // Add all foundCountacts if they have not been added already.\n for (const c of foundContacts) {\n if (!merged[c.user]) {\n merged[c.user] = c;\n }\n }\n\n return Object.values(merged);\n }\n\n resetContactList() {\n const newState = {\n chatList: []\n };\n\n if (!this.state.ready) {\n newState.ready = true;\n }\n\n this.tinode.getMeTopic().contacts((c) => {\n newState.chatList.push(c);\n if (this.state.topicSelected == c.topic) {\n newState.topicSelectedOnline = c.online;\n newState.topicSelectedAcs = c.acs;\n }\n });\n // Merge search results and chat list.\n newState.searchableContacts = TinodeWeb.prepareSearchableContacts(newState.chatList, this.state.searchResults);\n this.setState(newState);\n }\n\n // Sending \"received\" notifications\n tnData(data) {\n const topic = this.tinode.getTopic(data.topic);\n if (topic.msgStatus(data) >= Tinode.MESSAGE_STATUS_SENT && data.from != this.state.myUserId) {\n clearTimeout(this.receivedTimer);\n this.receivedTimer = setTimeout(() => {\n this.receivedTimer = undefined;\n topic.noteRecv(data.seq);\n }, RECEIVED_DELAY);\n }\n }\n\n /* Fnd topic: find contacts by tokens */\n tnInitFind() {\n const fnd = this.tinode.getFndTopic();\n fnd.onSubsUpdated = this.tnFndSubsUpdated;\n if (fnd.isSubscribed()) {\n this.tnFndSubsUpdated();\n } else {\n fnd.subscribe(fnd.startMetaQuery().withSub().build()).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n tnFndSubsUpdated() {\n const foundContacts = [];\n // Don't attempt to create P2P topics which already exist. Server will reject the duplicates.\n this.tinode.getFndTopic().contacts((s) => {\n foundContacts.push(s);\n });\n this.setState({\n searchResults: foundContacts,\n searchableContacts: TinodeWeb.prepareSearchableContacts(this.state.chatList, foundContacts)\n });\n }\n\n /** Called when the user enters a contact into the contact search field in the NewAccount panel\n @param query {Array} is an array of contacts to search for\n */\n handleSearchContacts(query) {\n const fnd = this.tinode.getFndTopic();\n fnd.setMeta({desc: {public: query}}).then((ctrl) => {\n return fnd.getMeta(fnd.startMetaQuery().withSub().build());\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n // User clicked on a contact in the side panel or deleted a contact.\n handleTopicSelected(topicName, unused_index, online, acs) {\n // Clear newTopicParams after use.\n if (this.state.newTopicParams && this.state.newTopicParams._topicName != topicName) {\n this.setState({\n newTopicParams: null\n });\n }\n\n if (topicName) {\n this.setState({\n errorText: '',\n errorLevel: null,\n mobilePanel: 'topic-view',\n showInfoPanel: false\n });\n // Different contact selected.\n if (this.state.topicSelected != topicName) {\n this.setState({\n topicSelectedOnline: online,\n topicSelectedAcs: acs\n });\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', topicName));\n }\n } else {\n // Currently selected contact deleted\n this.setState({\n errorText: '',\n errorLevel: null,\n mobilePanel: 'sidepanel',\n topicSelectedOnline: false,\n topicSelectedAcs: null,\n showInfoPanel: false\n });\n\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', null));\n }\n }\n\n // In mobile view user requested to show sidepanel\n handleHideMessagesView() {\n this.setState({\n mobilePanel: 'sidepanel'\n });\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, null));\n }\n\n // User is sending a message, either plain text or a drafty object, possibly\n // with attachments.\n handleSendMessage(msg, promise, uploader) {\n const topic = this.tinode.getTopic(this.state.topicSelected);\n\n msg = topic.createMessage(msg, false);\n // The uploader is used to show progress.\n msg._uploader = uploader;\n\n if (!topic.isSubscribed()) {\n if (!promise) {\n promise = Promise.resolve();\n }\n promise = promise.then(() => { return topic.subscribe(); });\n }\n\n if (promise) {\n promise = promise.catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n topic.publishDraft(msg, promise)\n .then((ctrl) => {\n if (topic.isArchived()) {\n return topic.archive(false);\n }\n })\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleNewChatInvitation(topicName, action) {\n const topic = this.tinode.getTopic(topicName);\n let response = null;\n switch (action) {\n case 'accept':\n // Accept given permissions.\n const mode = topic.getAccessMode().getGiven();\n response = topic.setMeta({sub: {mode: mode}});\n if (topic.getType() == 'p2p') {\n // For P2P topics change 'given' permission of the peer too.\n // In p2p topics the other user has the same name as the topic.\n response = response.then((ctrl) => {\n topic.setMeta({sub: {user: topicName, mode: mode}});\n });\n }\n break;\n case 'delete':\n // Ignore invitation by deleting it.\n response = topic.delTopic();\n break;\n case 'block':\n // Ban the topic making futher invites impossible.\n // Just self-ban.\n const am = topic.getAccessMode().updateWant('-JP').getWant();\n response = topic.setMeta({sub: {mode: am}}).then((ctrl) => {\n return this.handleTopicSelected(null);\n });\n break;\n default:\n console.log(\"Unknown invitation action\", '\"' + action + '\"\"');\n }\n\n if (response != null) {\n response.catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n // User chose a Sign Up menu item.\n handleNewAccount() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'register'));\n }\n\n // Actual registration of a new account.\n handleNewAccountRequest(login_, password_, public_, cred_, tags_) {\n this.tinode.connect(this.state.serverAddress)\n .then(() => {\n return this.tinode.createAccountBasic(login_, password_,\n {public: public_, tags: tags_, cred: Tinode.credential(cred_)});\n }).then((ctrl) => {\n if (ctrl.code >= 300 && ctrl.text == 'validate credentials') {\n this.handleCredentialsRequest(ctrl.params);\n } else {\n this.handleLoginSuccessful(this);\n }\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleUpdateAccountRequest(password, pub, defacs) {\n if (pub || defacs) {\n const params = {};\n if (pub) {\n params.public = pub;\n }\n if (defacs) {\n params.defacs = defacs;\n }\n this.tinode.getMeTopic().setMeta({desc: params}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n if (password) {\n this.tinode.updateAccountBasic(null, this.tinode.getCurrentLogin(), password).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleUpdateAccountTagsRequest(tags) {\n this.tinode.getMeTopic().setMeta({tags: tags})\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n // User chose Settings menu item.\n handleSettings() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash,\n this.state.myUserId ? 'edit' : 'settings'));\n }\n\n // User updated global parameters.\n handleGlobalSettings(settings) {\n let serverAddress = settings.serverAddress || this.state.serverAddress;\n let transport = settings.transport || this.state.transport;\n if (this.tinode) {\n this.tinode.onDisconnect = undefined;\n this.tinode.disconnect();\n }\n this.tinode = TinodeWeb.tnSetup(serverAddress, transport);\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n\n this.setState({\n serverAddress: serverAddress,\n transport: transport\n });\n LocalStorageUtil.setObject('settings', {\n serverAddress: serverAddress,\n transport: transport\n });\n\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, ''));\n }\n\n // User chose 'Archived chats'.\n handleShowArchive() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash,\n this.state.myUserId ? 'archive' : ''));\n }\n\n // Initialize desktop alerts = push notifications.\n initDesktopAlerts() {\n // Google could not be bothered to mention that\n // onTokenRefresh is never called.\n this.fbPush.onTokenRefresh(() => {\n this.requestPushToken();\n });\n\n this.fbPush.onMessage((payload) => {\n // No need to do anything about it.\n // All the magic happends in the service worker.\n });\n }\n\n togglePushToken(enabled) {\n if (enabled) {\n if (!this.state.firebaseToken) {\n this.fbPush.requestPermission().then(() => {\n this.requestPushToken();\n }).catch((err) => {\n this.handleError(err.message, 'err');\n this.setState({desktopAlerts: false, firebaseToken: null});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n console.log(\"Failed to get permission to notify.\", err);\n });\n } else {\n this.setState({desktopAlerts: true});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: true});\n }\n } else if (this.state.firebaseToken) {\n this.fbPush.deleteToken(this.state.firebaseToken).catch((err) => {\n console.log(\"Unable to delete token.\", err);\n }).finally(() => {\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n localStorage.removeItem('firebase-token');\n this.setState({desktopAlerts: false, firebaseToken: null});\n });\n } else {\n this.setState({desktopAlerts: false, firebaseToken: null});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n }\n }\n\n requestPushToken() {\n this.fbPush.getToken().then((refreshedToken) => {\n if (refreshedToken != this.state.firebaseToken) {\n this.tinode.setDeviceToken(refreshedToken, true);\n LocalStorageUtil.setObject('firebase-token', refreshedToken);\n }\n this.setState({firebaseToken: refreshedToken, desktopAlerts: true});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: true});\n }).catch((err) => {\n this.handleError(err.message, 'err');\n console.log(\"Failed to retrieve firebase token\", err);\n });\n }\n\n handleToggleMessageSounds(enabled) {\n this.setState({messageSounds: enabled});\n LocalStorageUtil.updateObject('settings', {\n messageSoundsOff: !enabled\n });\n }\n\n handleCredAdd(method, value) {\n const me = this.tinode.getMeTopic();\n me.setMeta({cred: {meth: method, val: value}}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleCredDelete(method, value) {\n const me = this.tinode.getMeTopic();\n me.delCredential(method, value).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleCredConfirm(method, response) {\n this.handleCredentialsRequest({cred: [method]});\n }\n\n // User clicked Cancel button in Setting or Sign Up panel.\n handleSidepanelCancel() {\n const parsed = HashNavigation.parseUrlHash(window.location.hash);\n parsed.path[0] = this.state.myUserId ? 'contacts' : '';\n if (parsed.params) {\n delete parsed.params.code;\n delete parsed.params.method;\n delete parsed.params.tab;\n }\n HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));\n this.setState({errorText: '', errorLevel: null});\n }\n\n // User clicked a (+) menu item.\n handleNewTopic() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'newtpk'));\n }\n\n // Request to start a new topic. New P2P topic requires peer's name.\n handleNewTopicRequest(peerName, pub, priv, tags) {\n\n const topicName = peerName || this.tinode.newGroupTopicName();\n const params = {\n _topicName: topicName,\n };\n if (peerName) {\n // Because we are initialing the subscription, set 'want' to all permissions.\n params.sub = {mode: DEFAULT_P2P_ACCESS_MODE};\n // Give the other user all permissions too.\n params.desc = {defacs: {auth: DEFAULT_P2P_ACCESS_MODE}};\n } else {\n params.desc = {public: pub, private: {comment: priv}};\n params.tags = tags;\n }\n this.setState({newTopicParams: params}, () => {this.handleTopicSelected(topicName)});\n }\n\n // New topic was creted, here is the new topic name.\n handleNewTopicCreated(oldName, newName) {\n if (this.state.topicSelected == oldName && oldName != newName) {\n // If the current URl contains the old topic name, replace it with new.\n // Update the name of the selected topic first so the navigator doen't clear\n // the state.\n this.setState({topicSelected: newName}, () => {\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', newName));\n });\n }\n }\n\n handleTopicUpdateRequest(topicName, pub, priv, permissions) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n const params = {};\n if (pub) {\n params.public = pub;\n }\n if (priv) {\n params.private = (priv === Tinode.DEL_CHAR) ?\n Tinode.DEL_CHAR : {comment: priv};\n }\n if (permissions) {\n params.defacs = permissions;\n }\n topic.setMeta({desc: params}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleChangePermissions(topicName, mode, uid) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n const am = topic.getAccessMode();\n if (uid) {\n am.updateGiven(mode);\n mode = am.getGiven();\n } else {\n am.updateWant(mode);\n mode = am.getWant();\n }\n topic.setMeta({sub: {user: uid, mode: mode}}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleTagsUpdated(topicName, tags) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n topic.setMeta({tags: tags}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleLogout() {\n updateFavicon(0);\n\n // Remove stored data.\n localStorage.removeItem('auth-token');\n localStorage.removeItem('firebase-token');\n localStorage.removeItem('settings');\n if (this.state.firebaseToken) {\n this.fbPush.deleteToken(this.state.firebaseToken)\n }\n\n if (this.tinode) {\n this.tinode.onDisconnect = undefined;\n this.tinode.disconnect();\n }\n this.setState(this.getBlankState());\n this.tinode = TinodeWeb.tnSetup(this.state.serverAddress, this.state.transport);\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n HashNavigation.navigateTo('');\n }\n\n handleDeleteMessagesRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n // Request hard-delete all messages.\n topic.delMessagesAll(true).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleLeaveUnsubRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n topic.leave(true).then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleBlockTopicRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n topic.updateMode(null, '-JP').then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleReportTopic(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n // Publish spam report.\n this.tinode.publish(Tinode.TOPIC_SYS, Tinode.Drafty.attachJSON(null, {\n 'action': 'report',\n 'target': topicName\n }));\n\n // Remove J and P permissions.\n topic.updateMode(null, '-JP').then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleShowContextMenu(params, menuItems) {\n this.setState({\n contextMenuVisible: true,\n contextMenuClickAt: {x: params.x, y: params.y},\n contextMenuParams: params,\n contextMenuItems: menuItems || this.defaultTopicContextMenu(params.topicName),\n contextMenuBounds: ReactDOM.findDOMNode(this).getBoundingClientRect()\n });\n }\n\n defaultTopicContextMenu(topicName) {\n const topic = this.tinode.getTopic(topicName);\n const {formatMessage} = this.props.intl;\n\n let muted = false, blocked = false, self_blocked = false, subscribed = false, deleter = false, archived = false;\n if (topic) {\n subscribed = topic.isSubscribed();\n archived = topic.isArchived();\n\n const acs = topic.getAccessMode();\n if (acs) {\n muted = acs.isMuted();\n blocked = !acs.isJoiner();\n self_blocked = !acs.isJoiner('want');\n deleter = acs.isDeleter();\n }\n }\n\n return [\n subscribed ? {title: formatMessage({id: 'menu_item_info'}), handler: this.handleShowInfoView} : null,\n subscribed ? 'messages_clear' : null,\n subscribed && deleter ? 'messages_clear_hard' : null,\n muted ? (blocked ? null : 'topic_unmute') : 'topic_mute',\n self_blocked ? 'topic_unblock' : 'topic_block',\n !archived ? 'topic_archive' : null,\n 'topic_delete'\n ];\n }\n\n handleHideContextMenu() {\n this.setState({\n contextMenuVisible: false,\n contextMenuClickAt: null,\n contextMenuParams: null,\n contextMenuBounds: null\n });\n }\n\n handleContextMenuAction(action, promise, params) {\n if (action == 'topic_archive') {\n if (promise && params.topicName && params.topicName == this.state.topicSelected) {\n promise.then(() => {\n this.handleTopicSelected(null);\n });\n }\n }\n }\n\n handleShowAlert(title, content, onConfirm, confirm, onReject, reject) {\n this.setState({\n alertVisible: true,\n alertParams: {\n title: title,\n content: content,\n onConfirm: onConfirm,\n confirm: confirm,\n onReject: onReject,\n reject: reject\n }\n });\n }\n\n handleShowInfoView() {\n HashNavigation.navigateTo(HashNavigation.addUrlParam(window.location.hash, 'info', true));\n this.setState({showInfoPanel: true});\n }\n\n handleHideInfoView() {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'info'));\n this.setState({showInfoPanel: false});\n }\n\n handleMemberUpdateRequest(topicName, added, removed) {\n if (!topicName) {\n return;\n }\n\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n if (added && added.length > 0) {\n added.map((uid) => {\n topic.invite(uid, null).catch((err) => {\n this.handleError(err.message, 'err');\n });\n });\n }\n\n if (removed && removed.length > 0) {\n removed.map((uid) => {\n topic.delSubscription(uid).catch((err) => {\n this.handleError(err.message, 'err');\n });\n });\n }\n }\n\n handleValidateCredentialsRequest(cred, code) {\n if (this.tinode.isAuthenticated()) {\n const me = this.tinode.getMeTopic();\n me.setMeta({cred: {meth: cred, resp: code}})\n .then(() => {\n HashNavigation.navigateTo('');\n })\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n } else {\n this.setState({credMethod: cred, credCode: code});\n this.doLogin(null, null, {meth: cred, resp: code});\n }\n }\n\n handlePasswordResetRequest(method, value) {\n // If already connected, connnect() will return a resolved promise.\n this.tinode.connect()\n .then(() => {\n return this.tinode.requestResetAuthSecret('basic', method, value);\n })\n .catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n }\n\n handleResetPassword(scheme, newPassword, token) {\n token = base64ReEncode(token);\n if (!token) {\n this.handleError(\"Invalid security token\", 'err');\n } else {\n this.tinode.connect()\n .then(() => {\n return this.tinode.updateAccountBasic(null, null, newPassword, {token: token});\n })\n .catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n }\n }\n\n render() {\n return (\n \n {this.state.contextMenuVisible ?\n
{\n if (topicName == this.state.topicSelected) {\n this.handleTopicSelected(null);\n }\n }}\n onError={this.handleError} />\n :\n null\n }\n { this.setState({alertVisible: false}); }) : null}\n reject={this.state.alertParams.reject}\n onConfirm={() => { this.setState({alertVisible: false}); this.state.alertParams.onConfirm(); }}\n confirm={this.state.alertParams.confirm}\n />\n \n\n \n\n {this.state.showInfoPanel ?\n \n :\n null\n }\n \n );\n }\n};\n\nexport default injectIntl(TinodeWeb);\n","// Put all packages together.\n// Used to generate umd/index.prod.js\n\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport { IntlProvider } from 'react-intl';\n\nimport allMessages from './messages.json';\nimport TinodeWeb from './views/tinode-web.jsx';\nimport HashNavigation from './lib/navigation.js';\n\n// Detect human language to use in the UI:\n// Check parameters from URL hash #?hl=ru, then browser, then use 'en' as a fallback.\nconst { params } = HashNavigation.parseUrlHash(window.location.hash);\nconst language = (params && params.hl) ||\n (navigator.languages && navigator.languages[0]) ||\n navigator.language ||\n navigator.userLanguage ||\n 'en';\n\n// Get the base language 'en' from a more specific 'en_GB' or 'en-US' as a partial fallback.\nconst baseLanguage = language.toLowerCase().split(/[-_]/)[0];\n\n// Try the full locale first, then the locale without the region code, fallback to 'en'.\nconst messages =\n allMessages[language] ||\n allMessages[baseLanguage] ||\n allMessages.en;\n\nReactDOM.render(\n \n \n ,\n document.getElementById('mountPoint')\n);\n"],"sourceRoot":""}
\ No newline at end of file
+{"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///external \"React\"","webpack:///external \"ReactIntl\"","webpack:///external \"Tinode\"","webpack:///external \"ReactDOM\"","webpack:///external \"firebase\"","webpack:///external [\"firebase\",\"messaging\"]","webpack:///./src/widgets/alert.jsx","webpack:///./src/version.js","webpack:///./src/config.js","webpack:///./src/lib/navigation.js","webpack:///./src/widgets/context-menu.jsx","webpack:///./src/lib/strformat.js","webpack:///./src/widgets/letter-tile.jsx","webpack:///./src/lib/blob-helpers.js","webpack:///./src/widgets/avatar-upload.jsx","webpack:///./src/widgets/checkbox.jsx","webpack:///./src/widgets/contact-badges.jsx","webpack:///./src/widgets/unread-badge.jsx","webpack:///./src/widgets/contact.jsx","webpack:///./src/widgets/contact-action.jsx","webpack:///./src/widgets/contact-list.jsx","webpack:///./src/widgets/menu-cancel.jsx","webpack:///./src/widgets/error-panel.jsx","webpack:///./src/widgets/chip.jsx","webpack:///./src/widgets/chip-input.jsx","webpack:///./src/widgets/group-manager.jsx","webpack:///./src/widgets/visible-password.jsx","webpack:///./src/widgets/in-place-edit.jsx","webpack:///./src/widgets/more-button.jsx","webpack:///./src/widgets/permissions-editor.jsx","webpack:///./src/lib/utils.js","webpack:///./src/widgets/tag-manager.jsx","webpack:///./src/views/info-view.jsx","webpack:///./src/widgets/file-progress.jsx","webpack:///./src/widgets/attachment.jsx","webpack:///./src/widgets/received-marker.jsx","webpack:///./src/widgets/chat-message.jsx","webpack:///./src/widgets/group-subs.jsx","webpack:///./src/widgets/send-message.jsx","webpack:///./src/widgets/image-preview.jsx","webpack:///./src/widgets/Invitation.jsx","webpack:///./src/widgets/load-spinner.jsx","webpack:///./src/views/logo-view.jsx","webpack:///./src/views/messages-view.jsx","webpack:///./src/widgets/menu-contacts.jsx","webpack:///./src/widgets/menu-start.jsx","webpack:///./src/widgets/side-navbar.jsx","webpack:///./src/views/contacts-view.jsx","webpack:///./src/lib/local-storage.js","webpack:///./src/views/create-account-view.jsx","webpack:///./src/views/edit-account-view.jsx","webpack:///./src/views/login-view.jsx","webpack:///./src/widgets/new-topic-by-id.jsx","webpack:///./src/widgets/new-topic-group.jsx","webpack:///./src/widgets/search-contacts.jsx","webpack:///./src/views/new-topic-view.jsx","webpack:///./src/views/password-reset-view.jsx","webpack:///./src/widgets/host-selector.jsx","webpack:///./src/views/settings-view.jsx","webpack:///./src/views/validation-view.jsx","webpack:///./src/views/sidepanel-view.jsx","webpack:///./src/lib/host-name.js","webpack:///./src/views/tinode-web.jsx","webpack:///./src/index.js"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","React","ReactIntl","Tinode","ReactDOM","firebase","Alert","PureComponent","this","props","visible","className","title","content","onReject","onClick","reject","id","onConfirm","confirm","APP_NAME","KNOWN_HOSTS","hosted","local","DEFAULT_HOST","DEFAULT_P2P_ACCESS_MODE","MAX_INBAND_ATTACHMENT_SIZE","HashNavigation","hash","parts","split","params","path","substr","forEach","part","item","decodeURIComponent","url","window","location","join","args","push","length","parsed","parseUrlHash","composeUrlHash","sidepanel","topic","info","messages","defineMessages","clear_messages","clear_for_all","delete_for_all","mute","unmute","topic_delete","topic_delete_warning","unblock","block","topic_block_warning","member_delete","archive","ContextMenu","Component","document","addEventListener","handlePageClick","handleEscapeKey","removeEventListener","e","findDOMNode","contains","target","preventDefault","stopPropagation","hide","keyCode","items","currentTarget","dataset","MenuItems","onAction","handler","onError","console","log","all","hard","errorHandler","tinode","getTopic","topicName","cancelSend","seq","delMessagesAll","delMessagesList","catch","err","message","result","updateMode","user","count","menu","map","handleClick","data-id","vSize","position","left","bounds","right","clickAt","x","top","bottom","y","style","formatMessage","intl","onShowAlert","deleteMessages","delete","topicPermissionSetter","then","ctrl","onTopicRemoved","delTopic","delSubscription","injectIntl","shortDateFormat","locale","navigator","userLanguage","language","now","Date","getFullYear","getMonth","getDate","toLocaleTimeString","hour12","hour","minute","toLocaleDateString","month","day","year","bytesToHumanSize","bytes","sizes","bucket","Math","min","floor","log2","pow","round","toFixed","LetterTile","avatar","isGroup","topicType","iconColor","abs","charCodeAt","stringHash","trim","letter","charAt","alt","src","onerror","SUPPORTED_IMAGE_FORMATS","MIME_EXTENSIONS","makeImageUrl","photo","type","data","fitImageSize","width","height","maxWidth","maxHeight","forceSquare","scale","size","dstWidth","dstHeight","srcWidth","srcHeight","xoffset","yoffset","fileNameForMime","fname","mime","idx","indexOf","ext","at","lastIndexOf","substring","imageFileScaledToBase64","file","onSuccess","img","Image","crossOrigin","onload","dim","canvas","createElement","ctx","getContext","imageSmoothingEnabled","drawImage","imageBits","toDataURL","getMimeType","quality","base64DecodedLen","URL","createObjectURL","imageFileToBase64","reader","FileReader","readAsDataURL","fileToBase64","header","exec","AvatarUpload","prevProps","setState","dataUrl","files","base64bits","du","onImageChanged","randId","random","readOnly","state","href","uid","accept","onChange","handleFileUpload","htmlFor","CheckBox","checked","handleChange","icon_mapping","ContactBadges","badges","b","icon","color","UnreadBadge","Contact","onSelected","index","acs","showContextMenu","pageY","pageX","online","slice","icon_badges","showMode","getMode","isMuted","isJoiner","showCheckmark","selected","showOnline","unread","comment","handleContextClick","ContactAction","action","values","badge_you","badge_owner","ContactList","Array","isArray","topicSelected","contactNodes","contactsCount","contacts","filterFunc","filter","private","toLowerCase","public","fn","myUserId","isOwner","showUnread","connected","onTopicSelected","noScroll","dangerouslySetInnerHTML","__html","emptyListMessage","MenuCancel","onCancel","ErrorPanel","level","show","onClearError","warn","text","whiteSpace","actionText","Chip","invalid","noAvatar","required","handleCancel","ChipInput","placeholder","chips","prompt","sortedChips","sortChips","staticMembers","chipIndex","indexChips","keep","normal","includes","concat","prevState","deriveStateFromProps","input","removed","onChipRemoved","removeChipAt","focused","onFocusLost","onEnter","handleChipCancel","undefined","avatarDisabled","handleTextInput","onFocus","handleFocusGained","onBlur","handleFocusLost","onKeyDown","handleKeyDown","autoFocus","no_contacts","contacts_not_found","GroupManager","members","delta","present","keepInitial","requiredMember","stat","sel","userId","status","selectedContacts","splice","val","msg","query","contactFilter","noContactsMessage","instance","added","keys","k","onSubmit","defaultMessage","handleContactFilter","handleMemberRemoved","doContactFiltering","handleContactSelected","handleSubmit","indexMembers","keepInitialMembers","VisiblePassword","onFinished","handleEditingFinished","setTimeout","activeElement","tabIndex","autoComplete","handeTextChange","handleVisibility","InPlaceEdit","newValue","initialValue","active","focus","event","checkValidity","fieldType","spanText","spanClass","handlePasswordFinished","handleStartEditing","MoreButton","open","onToggle","handleToggle","PermissionsEditor","replace","sort","names","joiner","writer","preser","approver","sharer","deleter","owner","skip","compare","userTitle","userAvatar","modeTitle","compareTitle","updateFavicon","oldIcon","getElementById","head","getElementsByTagName","newIcon","rel","removeChild","appendChild","vcard","imageDataUrl","card","dataStart","arrayEqual","a","sanitizeUrl","allowedSchemes","test","schemes","RegExp","TagManager","nextProps","tags","activated","tagInput","last","handleAddTag","tag","onTagsChanged","add_tags_prompt","handleRemoveTag","handleTagInput","defautMessage","description","requested","granted","edit_permissions","other_user","clear_messages_warning","delete_messages","delete_messages_warning","leave_chat","leave_chat_warning","block_contact","block_contact_warning","report_chat","report_chat_warning","InfoView","onMetaDesc","previousMetaDesc","previousSubsUpdated","onSubsUpdated","getType","previousTagsUpdated","onTagsUpdated","resetDesc","resetSubs","newState","contactList","user2","subscriber","modeGiven2","getGiven","modeWant2","getWant","subscribers","sub","defacs","getDefaultAccess","getAccessMode","admin","isAdmin","isSharer","isDeleter","muted","fullName","address","groupTopic","showMemberPanel","access","modeGiven","modeWant","auth","anon","getMeta","startMetaQuery","withTags","build","desc","subs","onTopicDescUpdate","DEL_CHAR","ignored","onChangePermissions","perm","showPermissionEditorFor","userPermissionsEdited","which","toEdit","toCompare","toSkip","titleEdit","titleCompare","userPermissionsTitle","userPermissionsAvatar","editedPermissions","immutablePermissions","editedPermissionsTitle","immutablePermissionsTitle","editedPermissionsSkipped","onInitFind","onMemberUpdateRequest","onDeleteMessages","onLeaveTopic","onBlockTopic","onReportTopic","selectedContact","moreInfoExpanded","onTopicTagsUpdate","menuItems","handleLaunchPermissionsEditor","displayMobile","errorLevel","errorText","searchableContacts","handleHideAddMembers","handleMemberUpdateRequest","handlePermissionsChanged","handleHidePermissionsEditor","handleFullNameUpdate","private_placeholder","handlePrivateUpdate","handleImageChanged","handleMuted","more","handleMoreInfo","handleTagsUpdated","handleDeleteMessages","handleLeave","handleBlock","handleReport","handleShowAddMembers","no_members","handleMemberSelected","handleContextMenu","FileProgress","progress","Attachment","filename","mimetype","downloader","getLargeFileHelper","download","loaded","uploader","onCancelUpload","cancel","helperFunc","downloadUrl","downloadFile","ReceivedMarker","timestamp","received","MESSAGE_STATUS_SENDING","sending","MESSAGE_STATUS_FAILED","failed","marker","MESSAGE_STATUS_SENT","MESSAGE_STATUS_RECEIVED","MESSAGE_STATUS_READ","ChatMessage","onImagePreview","resp","act","ref","onFormResponse","ratio","sideClass","deleted","sequence","response","bubbleClass","fullDisplay","userFrom","attachments","mimeType","Drafty","getContentType","att","getDownloadUrl","isUploading","getEntitySize","handleCancelUpload","format","draftyFormatter","userName","notFound","onProgress","handleProgress","handleImagePreview","handleFormButtonClick","el","tagName","attr","attrValue","viewportWidth","REM_SIZE","sanitizedUrl","sanitizeImageUrl","inner","Children","child","GroupSubs","usersOnline","totalCount","countToShow","some","overflow","SendMessage","messageEditArea","handlePasteEvent","disabled","onImageSuccess","onAttachmentSuccess","clipboardData","originalEvent","kind","getAsFile","filePasted","bits","onAttachImage","onAttachFile","file_attachment_too_large","limit","cannot_initiate_upload","uploadCompletionPromise","onSendMessage","shiftKey","handleSend","getTime","keypressTimestamp","isSubscribed","noteKeyPress","messaging_disabled","type_new_message","attachImage","click","attachFile","handleMessageTyping","onKeyPress","handleKeyPress","handleAttachFile","display","handleAttachImage","ImagePreview","node","getBoundingClientRect","maxlength","max","onClose","downloadable","assignWidth","Invitation","evt","handleButtonAction","LoadSpinner","LogoView","version","getLibrary","hideSelf","serverVersion","serverAddress","online_now","last_seen","isUnconfirmed","ex","getExcessive","isPeerRestricted","ms","getMissing","MessagesView","nextState","imagePreview","typingIndicator","scrollPosition","fetchingMessages","msgs","assign","onlineSubs","peer","p2pPeerDesc","peerMessagingDisabled","isWriter","isReader","readingBlocked","unconformed","unconfirmed","messagesScroller","handleScrollEvent","scrollTop","scrollHeight","viewportHeight","isNewGroupTopicName","leave","readTimerHandler","onData","handleNewMessage","onAllMessagesReceived","handleAllMessagesReceived","onInfo","handleInfoReceipt","handleDescChange","handleSubsUpdated","onPres","ready","newTopic","newTopicParams","_topicName","getQuery","withLaterDesc","withLaterSub","withLaterData","withLaterDel","setQuery","subscribe","onNewTopicCreated","queuedMessages","pub","_sending","publishMessage","blankState","getDerivedStateFromProps","not_found","oldTopicName","oldTopic","finally","msgHasMoreMessages","getMessagesPage","isNewMessage","msgStatus","from","noteRead","what","clearTimeout","keyPressTimer","KEYPRESS_DELAY","forceUpdate","sendMessage","attachJSON","parse","searchParams","set","search","onNewChat","handleNewImagePreview","component","component2","handleCloseImagePreview","messageNodes","previousFrom","chatBoxClass","nextFrom","isReply","deliveryStatus","userDesc","hi","ts","_uploader","handleShowContextMenuMessage","handleFormResponse","lastSeen","cont","getMeTopic","getContact","seen","when","onHideMessagesView","handleScrollReference","handleEnablePeer","handleNewChatAcceptance","MenuContacts","onNewTopic","onSettings","MenuStart","onSignUp","SideNavbar","archived_contacts_title","ContactsView","unreadThreads","archivedCount","chatList","arch","touched","onShowArchive","handleAction","LocalStorageUtil","localStorage","setItem","JSON","stringify","getItem","oldVal","getObject","setObject","removeItem","CreateAccountView","login","password","email","saveToken","errorCleared","onCreateAccount","submitClasses","login_prompt","handleLoginChange","password_prompt","handlePasswordChange","full_name_prompt","handleFnChange","email_prompt","handleEmailChange","handleToggleSaveToken","stay_logged_in","EditAccountView","me","onCredsUpdated","tnCredsUpdated","tnNewTags","previousOnTags","creds","credentials","onUpdateAccount","pwd","onToggleMessageSounds","onTogglePushNotifications","newCred","addCredInvalid","addCredActive","handleCredEntered","method","onCredAdd","onUpdateTags","cred","meth","done","onCredConfirm","onCredDelete","Fragment","full_name_placeholder","password_unchanged","handlePasswordUpdate","messageSounds","handleCheckboxClick","desktopAlertsEnabled","desktopAlerts","title_tag_manager","handleCredChange","handleCredKeyDown","onLogout","getCredentials","LoginView","onLogin","autoCorrect","autoCapitalize","hostName","invalid_id","NewTopicById","groupId","NewTopicGroup","handlePrivateChange","handleTagsChanged","SearchContacts","edited","onSearchContacts","handleSearch","handleClear","search_placeholder","handleSearchChange","search_for_contacts","search_no_results","NewTopicView","navigateTo","addUrlParam","tabSelected","searchQuery","isNullValue","removeUrlParam","onCreateTopic","priv","no_contacts_placeholder","handleTabClick","handleNewGroupSubmit","handleGroupByID","handleSearchContacts","searchResults","PasswordResetView","token","scheme","onReset","onRequest","reset","HostSelector","changed","onServerAddressChange","hostOptions","list","handleHostNameChange","SettingsView","onUpdate","transport","def","ws","lp","transportOptions","handleTransportSelected","handleServerAddressChange","phone","ValidationView","code","credMethod","numbers_only","credCode","SidepanelView","onLoginRequest","view","errorAction","errorActionText","loadSpinnerVisible","loginDisabled","handleLoginRequested","onGlobalSettings","onUpdateAccountTags","onValidateCredentials","onPasswordResetRequest","onResetPassword","isSecureConnection","protocol","POP_SOUND","Audio","update_available","reconnect_countdown","reconnect_now","TinodeWeb","enableLogging","foundContacts","merged","updated","host","settings","hostname","port","messageSoundsOff","FIREBASE_INIT","firebaseToken","sidePanelSelected","sidePanelTitle","sidePanelAvatar","liveConnection","onLine","topicSelectedOnline","topicSelectedAcs","innerWidth","showInfoPanel","mobilePanel","contextMenuVisible","contextMenuBounds","contextMenuClickAt","contextMenuParams","contextMenuItems","alertVisible","alertParams","handleResize","handleOnline","handleHashRoute","handleVisibilityEvent","documentElement","clientWidth","clientHeight","tnSetup","onConnect","handleConnected","onDisconnect","handleDisconnect","onAutoreconnectIteration","handleAutoreconnectIteration","fbPush","messaging","usePublicVapidKey","messagingVapidKey","serviceWorker","register","reg","checkForAppUpdate","useServiceWorker","initDesktopAlerts","setDeviceToken","togglePushToken","handleError","parsedNav","expires","setAuthToken","connect","tab","readTimer","readTimerCallback","mobile","formatHTMLMessage","onupdatefound","installingWorker","installing","onstatechange","controller","newTopicTabSelected","callback","handleAppVisibility","hidden","isConnected","doLogin","getServerInfo","ver","sec","prom","clearInterval","reconnectCountdown","setInterval","seconds","timeLeft","reconnect","isAuthenticated","credential","promise","getAuthToken","loginBasic","loginToken","handleCredentialsRequest","handleLoginSuccessful","tnMeMetaDesc","onContactUpdate","tnMeContactUpdate","tnMeSubsUpdated","getCurrentUserID","withDesc","withCred","disconnect","setUrlSidePanel","resetContactList","archived","isArchived","play","handleTopicSelected","prepareSearchableContacts","receivedTimer","noteRecv","fnd","getFndTopic","tnFndSubsUpdated","withSub","setMeta","unused_index","setUrlTopic","createMessage","Promise","resolve","publishDraft","am","updateWant","login_","password_","public_","cred_","tags_","createAccountBasic","updateAccountBasic","getCurrentLogin","onTokenRefresh","requestPushToken","onMessage","payload","enabled","updateObject","requestPermission","deleteToken","getToken","refreshedToken","delCredential","peerName","newGroupTopicName","oldName","newName","permissions","updateGiven","getBlankState","publish","TOPIC_SYS","defaultTopicContextMenu","blocked","self_blocked","subscribed","handleShowInfoView","invite","requestResetAuthSecret","newPassword","str","btoa","atob","base64ReEncode","handleHideContextMenu","handleShowAlert","handleContextMenuAction","handleGlobalSettings","handleNewAccount","handleSettings","handleLoginRequest","handleNewAccountRequest","handleUpdateAccountRequest","handleUpdateAccountTagsRequest","handleToggleMessageSounds","handleCredAdd","handleCredDelete","handleCredConfirm","handleNewTopicRequest","handleNewTopic","handleLogout","handleSidepanelCancel","handleValidateCredentialsRequest","handlePasswordResetRequest","handleResetPassword","handleShowArchive","tnInitFind","handleShowContextMenu","handleHideMessagesView","tnData","handleNewTopicCreated","handleReadTimer","handleChangePermissions","handleNewChatInvitation","handleSendMessage","handleTopicUpdateRequest","handleHideInfoView","handleDeleteMessagesRequest","handleLeaveUnsubRequest","handleBlockTopicRequest","handleReportTopic","onAddMember","handleManageGroupMembers","hl","languages","baseLanguage","allMessages","en","render","textComponent"],"mappings":"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","file":"index.prod.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/umd/\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 7);\n","module.exports = React;","module.exports = ReactIntl;","module.exports = Tinode;","module.exports = ReactDOM;","module.exports = firebase;","module.exports = firebase[\"messaging\"];","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class Alert extends React.PureComponent {\n render() {\n return this.props.visible ?\n \n
\n
{this.props.title}
\n
{this.props.content}
\n
\n {this.props.onReject ?\n \n {this.props.reject || }\n \n :\n null\n }\n \n {this.props.confirm || }\n \n
\n
\n
\n :\n null;\n }\n};\n","// This is a generated file. Don't edit.\n\nexport const PACKAGE_VERSION = \"0.16.3-rc5\";\n","import { PACKAGE_VERSION } from './version.js';\n\n// Name of this application, used in the User-Agent.\nexport const APP_NAME = 'TinodeWeb/' + (PACKAGE_VERSION || '0.16');\n\n// API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own\nexport const API_KEY = 'AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K';\n\n// The array of possible hosts to connect to.\nexport const KNOWN_HOSTS = {hosted: 'web.tinode.co', local: 'localhost:6060'};\n\n// Default host name and port to connect to.\nexport const DEFAULT_HOST = KNOWN_HOSTS.hosted;\n\n// Enable console logging.\nexport const LOGGING_ENABLED = true;\n\n// Minimum time between two keypress notifications, milliseconds.\nexport const KEYPRESS_DELAY = 3*1000;\n// Delay before sending a {note} for reciving a message, milliseconds.\nexport const RECEIVED_DELAY = 500;\n// Delay before sending a read notification, milliseconds.\nexport const READ_DELAY = 1000;\n\n// The shortest allowed tag length. Matches the value on the server.\nexport const MIN_TAG_LENGTH = 4;\n\n// Access mode for P2P subscriptions initiated by the current user.\nexport const DEFAULT_P2P_ACCESS_MODE = 'JRWPS';\n// Access mode for new group topics created by the current user.\nexport const NEW_GRP_ACCESS_MODE = 'JRWPSAO';\n\n// Access mode for no access.\nexport const NO_ACCESS_MODE = 'N';\n\n// Mediaquery breakpoint between desktop and mobile, in px. Should match the value\n// in @media (max-size: 640px) in base.css\nexport const MEDIA_BREAKPOINT = 640;\n// Size of css 'rem' unit in pixels. Default 1rem = 10pt = 13px.\nexport const REM_SIZE = 13;\n\n// Size of the avatar image: When an avatar image is uploaded, it's resized to\n// a square of this size.\nexport const AVATAR_SIZE = 128;\n\n// Size of the broken_image shown in MessagesView\nexport const BROKEN_IMAGE_SIZE = 32;\n\n// Number of chat messages to fetch in one call.\nexport const MESSAGES_PAGE = 24;\n\n// Maximum in-band (included directly into the message) attachment size which fits into\n// a message of 256K in size, assuming base64 encoding and 1024 bytes of overhead.\n// This is size of an object *before* base64 encoding is applied.\n// Increase this limit to a greater value in production, if desired. Also increase\n// max_message_size in server config.\n// MAX_INBAND_ATTACHMENT_SIZE = base64DecodedLen(max_message_size - overhead);\nexport const MAX_INBAND_ATTACHMENT_SIZE = 195840;\n\n// Absolute maximum attachment size to be used with the server = 8MB. Increase to\n// something like 100MB in production.\nexport const MAX_EXTERN_ATTACHMENT_SIZE = 1 << 23;\n\n// Maximum allowed linear dimension of an inline image in pixels. You may want\n// to adjust it to 1600 or 2400 for production.\nexport const MAX_IMAGE_DIM = 768;\n\n// Maximum number of online users to be shown in a topic title bar. Others will be\n// hidden under \"+X more\"\nexport const MAX_ONLINE_IN_TOPIC = 4;\n\n// Maximum length of user name, topic title, and private comment.\nexport const MAX_TITLE_LENGTH = 60;\n\n// Link for \"Contact Us\".\nexport const LINK_CONTACT_US = 'email:info@tinode.co';\n\n// Link to Privacy Policy.\nexport const LINK_PRIVACY_POLICY = 'https://tinode.co/privacy.html';\n\n// Link to Terms of Service.\nexport const LINK_TERMS_OF_SERVICE = 'https://tinode.co/terms.html';\n","// Utility class for hash navigation.\n\n// Parse hash as in http://www.example.com/path#hash as if it were\n// path and arguments.\nexport default class HashNavigation {\n static parseUrlHash(hash) {\n // Split path from args, path -> parts[0], args->path[1]\n let parts = hash.split('?', 2);\n let params = {};\n let path = [];\n if (parts[0]) {\n path = parts[0].substr(1).split('/');\n }\n if (parts[1]) {\n parts[1].split('&').forEach(function(part) {\n let item = part.split('=');\n if (item[0]) {\n params[decodeURIComponent(item[0])] = decodeURIComponent(item[1]);\n }\n });\n }\n return {path: path, params: params};\n }\n\n static navigateTo(url) {\n window.location.hash = url;\n }\n\n static composeUrlHash(path, params) {\n var url = path.join('/');\n var args = [];\n for (var key in params) {\n if (params.hasOwnProperty(key)) {\n args.push(key + '=' + params[key]);\n }\n }\n if (args.length > 0) {\n url += '?' + args.join('&');\n }\n return url;\n }\n\n static addUrlParam(hash, key, value) {\n var parsed = this.parseUrlHash(hash);\n parsed.params[key] = value;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static removeUrlParam(hash, key) {\n var parsed = this.parseUrlHash(hash);\n delete parsed.params[key];\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static setUrlSidePanel(hash, sidepanel) {\n var parsed = this.parseUrlHash(hash);\n parsed.path[0] = sidepanel;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static setUrlTopic(hash, topic) {\n var parsed = this.parseUrlHash(hash);\n parsed.path[1] = topic;\n // Close InfoView on topic change.\n delete parsed.params.info;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n}\n","// Context Menu: popup/dropdown menu.\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport { injectIntl, defineMessages } from 'react-intl';\n\nimport { REM_SIZE } from '../config.js';\nimport HashNavigation from '../lib/navigation.js';\n\nconst messages = defineMessages({\n info: {\n id: 'menu_item_info',\n defaultMessage: 'Info',\n description: 'Show extended topic information'\n },\n clear_messages: {\n id: 'menu_item_clear_messages',\n defaultMessage: 'Clear messages',\n description: 'Delete all messages'\n },\n clear_for_all: {\n id: 'menu_item_clear_messages_for_all',\n defaultMessage: 'Clear for All',\n description: 'Delete all message(s) for all members'\n },\n 'delete': {\n id: 'menu_item_delete',\n defaultMessage: 'Delete',\n description: 'Delete selected messages'\n },\n delete_for_all: {\n id: 'menu_item_delete_for_all',\n defaultMessage: 'Delete for All',\n description: 'Delete selected message(s) for all members'\n },\n mute: {\n id: 'menu_item_mute',\n defaultMessage: 'Mute',\n description: 'Turn off notifications'\n },\n unmute: {\n id: 'menu_item_unmute',\n defaultMessage: 'Unmute',\n description: 'Turn notifications on'\n },\n topic_delete: {\n id: 'menu_item_delete_topic',\n defaultMessage: 'Delete',\n description: 'Delete entire topic'\n },\n topic_delete_warning: {\n id: 'topic_delete_warning',\n defaultMessage: 'Are you sure you want to delete this conversation?',\n description: 'Alert warning when deleting entire topic'\n },\n unblock: {\n id: 'menu_item_unblock',\n defaultMessage: 'Unblock',\n description: 'Unblock topic or user'\n },\n // Generic string suitable for either topic or user.\n block: {\n id: 'menu_item_block',\n defaultMessage: 'Block',\n description: 'Block topic or user'\n },\n topic_block_warning: {\n id: 'topic_block_warning',\n defaultMessage: 'Are you sure you want to block this conversation?',\n description: 'Alert warning when blocking a topic.'\n },\n member_delete: {\n id: 'menu_item_member_delete',\n defaultMessage: 'Remove',\n description: 'Remove user from topic'\n },\n archive: {\n id: 'menu_item_archive_topic',\n defaultMessage: 'Archive',\n description: 'Move topic from the list of active chats to archive'\n }\n});\n\nclass ContextMenu extends React.Component {\n constructor(props) {\n super(props);\n\n const {formatMessage} = props.intl;\n\n this.handlePageClick = this.handlePageClick.bind(this);\n this.handleEscapeKey = this.handleEscapeKey.bind(this);\n this.handleClick = this.handleClick.bind(this);\n\n // Preconfigured menu items.\n this.MenuItems = {\n 'topic_info': {\n id: 'topic_info',\n title: formatMessage(messages.info),\n handler: null\n },\n 'messages_clear': {\n id: 'messages_clear',\n title: formatMessage(messages.clear_messages),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage({id: 'menu_item_clear_messages'}), // title\n formatMessage({id: 'clear_messages_warning'}), // content\n (() => { this.deleteMessages(true, false, params, errorHandler); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'messages_clear_hard': {\n id: 'messages_clear_hard',\n title: formatMessage(messages.clear_for_all),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage({id: 'menu_item_clear_messages_for_all'}), // title\n formatMessage({id: 'delete_messages_warning'}), // content\n (() => { return this.deleteMessages(true, true, params, errorHandler); }),\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'message_delete': {\n id: 'message_delete',\n title: formatMessage(messages.delete),\n handler: (params, errorHandler) => {\n return this.deleteMessages(false, false, params, errorHandler);\n }\n },\n 'message_delete_hard': {\n id: 'message_delete_hard',\n title: formatMessage(messages.delete_for_all),\n handler: (params, errorHandler) => {\n return this.deleteMessages(false, true, params, errorHandler);\n }\n },\n 'topic_unmute': {\n id: 'topic_unmute',\n title: formatMessage(messages.unmute),\n handler: this.topicPermissionSetter.bind(this, '+P')\n },\n 'topic_mute': {\n id: 'topic_mute',\n title: formatMessage(messages.mute),\n handler: this.topicPermissionSetter.bind(this, '-P')\n },\n 'topic_unblock': {\n id: 'topic_unblock',\n title: formatMessage(messages.unblock),\n handler: this.topicPermissionSetter.bind(this, '+JP')\n },\n 'topic_block': {\n id: 'topic_block',\n title: formatMessage(messages.block),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage({id: 'menu_item_block'}), // title\n formatMessage(messages.topic_block_warning), // content\n (() => {\n return this.topicPermissionSetter('-JP', params, errorHandler).then((ctrl) => {\n this.props.onTopicRemoved(params.topicName);\n return ctrl;\n });\n }),\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'topic_delete': {\n id: 'topic_delete',\n title: formatMessage(messages.topic_delete),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage({id: 'menu_item_delete_topic'}), // title\n formatMessage(messages.topic_delete_warning), // content\n (() => {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found: \", params.topicName);\n return;\n }\n return topic.delTopic().catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }),\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'topic_archive': {\n id: 'topic_archive',\n title: formatMessage(messages.archive),\n handler: (params, errorHandler) => {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found: \", params.topicName);\n return;\n }\n return topic.archive(true).catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }\n },\n // menu_item_edit_permissions is defined elsewhere.\n 'permissions': {\n id: 'permissions',\n title: formatMessage({id: 'menu_item_edit_permissions'}),\n handler: null\n },\n 'member_delete': {\n id: 'member_delete',\n title: formatMessage(messages.member_delete),\n handler: (params, errorHandler) => {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic || !params.user) {\n console.log(\"Topic or user not found: '\" + params.topicName + \"', '\" + params.user + \"'\");\n return;\n }\n return topic.delSubscription(params.user).catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }\n },\n 'member_mute': {\n id: 'member_mute',\n title: formatMessage(messages.mute),\n handler: this.topicPermissionSetter.bind(this, '-P')\n },\n 'member_unmute': {\n id: 'member_unmute',\n title: formatMessage(messages.unmute),\n handler: this.topicPermissionSetter.bind(this, '+P')\n },\n 'member_block': {\n id: 'member_block',\n title: formatMessage(messages.block),\n handler: this.topicPermissionSetter.bind(this, '-J')\n },\n 'member_unblock': {\n id: 'member_unblock',\n title: formatMessage(messages.unblock),\n handler: this.topicPermissionSetter.bind(this, '+J')\n },\n };\n }\n\n componentDidMount() {\n document.addEventListener('mousedown', this.handlePageClick, false);\n document.addEventListener('keyup', this.handleEscapeKey, false);\n }\n\n componentWillUnmount() {\n document.removeEventListener('mousedown', this.handlePageClick, false);\n document.removeEventListener('keyup', this.handleEscapeKey, false);\n }\n\n handlePageClick(e) {\n if (ReactDOM.findDOMNode(this).contains(e.target)) {\n return;\n }\n e.preventDefault();\n e.stopPropagation();\n this.props.hide();\n }\n\n handleEscapeKey(e) {\n if (e.keyCode === 27) {\n this.props.hide();\n }\n }\n\n handleClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.hide();\n let item = this.props.items[e.currentTarget.dataset.id];\n if (typeof item == 'string') {\n item = this.MenuItems[item];\n }\n\n if (!item) {\n console.log(\"Invalid menu item ID\", e.currentTarget.dataset.id);\n } else {\n this.props.onAction(\n item.id,\n item.handler(this.props.params, this.props.onError),\n this.props.params);\n }\n }\n\n // Menu Actions\n\n deleteMessages(all, hard, params, errorHandler) {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found: \", params.topicName);\n return;\n }\n\n // We don't know if the message is still pending (e.g. attachment is being uploaded),\n // so try cancelling first. No harm if we can't cancel.\n // The message can be cancelled if transmission to the server has not\n // started yet or if the message send has failed.\n if (!all && topic.cancelSend(params.seq)) {\n return;\n }\n // Can't cancel. Delete instead.\n const promise = all ?\n topic.delMessagesAll(hard) :\n topic.delMessagesList([params.seq], hard);\n\n return promise.catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }\n\n // Function is used by context menu to set permissions.\n topicPermissionSetter(mode, params, errorHandler) {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found\", params.topicName);\n return;\n }\n\n let result = topic.updateMode(params.user, mode);\n if (errorHandler) {\n result = result.catch((err) => {\n errorHandler(err.message, 'err');\n });\n }\n return result;\n }\n\n render() {\n let count = 0;\n let menu = [];\n this.props.items.map((item) => {\n if (typeof item == 'string') {\n item = this.MenuItems[item];\n }\n if (item && item.title) {\n menu.push(\n item.title == '-' ?\n \n :\n {item.title} \n );\n }\n count++;\n });\n\n // Ensure that menu is inside the app-container.\n const hSize = 12 * REM_SIZE;\n const vSize = REM_SIZE * (0.7 + menu.length * 2.5);\n const left = (this.props.bounds.right - this.props.clickAt.x < hSize) ?\n (this.props.clickAt.x - this.props.bounds.left - hSize) :\n (this.props.clickAt.x - this.props.bounds.left);\n const top = (this.props.bounds.bottom - this.props.clickAt.y < vSize) ?\n (this.props.clickAt.y - this.props.bounds.top - vSize) :\n (this.props.clickAt.y - this.props.bounds.top);\n\n const position = {\n left: left + 'px',\n top: top + 'px'\n };\n\n return (\n \n );\n }\n}\n\nexport default injectIntl(ContextMenu);\n","// Short representation of time in the past.\nexport function shortDateFormat(then, locale) {\n locale = locale || window.navigator.userLanguage || window.navigator.language;\n const now = new Date();\n if (then.getFullYear() == now.getFullYear()) {\n if (then.getMonth() == now.getMonth() && then.getDate() == now.getDate()) {\n\t return then.toLocaleTimeString(locale, {hour12: false, hour: '2-digit', minute: '2-digit'});\n } else {\n\t return then.toLocaleDateString(locale,\n {hour12: false, month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'});\n }\n }\n return then.toLocaleDateString(locale,\n {hour12: false, year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'});\n}\n\n// Convert seconds to minutes:seconds, i.e. 156 sec -> 2:36.\nexport function secondsToTime(seconds) {\n const min = Math.floor(seconds / 60);\n let sec = seconds % 60;\n sec = sec < 10 ? `0${sec}` : sec;\n return `${min}:${sec}`;\n}\n\n// Convert a number of bytes to human-readable format.\nexport function bytesToHumanSize(bytes) {\n if (!bytes || bytes == 0) {\n return '0 Bytes';\n }\n\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];\n const bucket = Math.min(Math.floor(Math.log2(bytes) / 10) | 0, sizes.length-1);\n const count = bytes / Math.pow(1024, bucket);\n const round = bucket > 0 ? (count < 3 ? 2 : (count < 30 ? 1 : 0)) : 0;\n return count.toFixed(round) + ' ' + sizes[bucket];\n}\n\n// Get 32 bit integer hash value for a string. Ideally it should produce the same value\n// as Java's String#hash().\nexport function stringHash(value) {\n let hash = 0;\n value = '' + value;\n for (let i = 0; i < value.length; i++) {\n hash = ((hash << 5) - hash) + value.charCodeAt(i);\n hash = hash & hash; // Convert to 32bit integer\n }\n return hash;\n}\n","import React from 'react';\n\nimport Tinode from 'tinode-sdk';\n\nimport { stringHash } from '../lib/strformat.js';\n\nexport default class LetterTile extends React.PureComponent {\n render() {\n let avatar;\n if (this.props.avatar === true) {\n const isGroup = (Tinode.topicType(this.props.topic) == 'grp');\n const iconColor = (isGroup ? 'light-color' : 'dark-color')\n + (Math.abs(stringHash(this.props.topic)) % 16);\n if (this.props.topic && this.props.title && this.props.title.trim()) {\n const letter = this.props.title.trim().charAt(0);\n const className = 'lettertile ' + iconColor;\n avatar = ()\n } else {\n const className = 'material-icons ' + iconColor;\n avatar = isGroup ?\n group : person ;\n }\n } else if (this.props.avatar) {\n // If avatar image is invalid, show a placeholder.\n avatar = {e.target.onerror = null; e.target.src=\"/img/broken_image.png\"}} />;\n } else {\n avatar = null;\n }\n return avatar;\n }\n}\n","// File and image helper functions.\nimport { MAX_INBAND_ATTACHMENT_SIZE, MAX_IMAGE_DIM } from '../config.js';\n\n// Supported image MIME types and corresponding file extensions.\nexport const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];\nexport const MIME_EXTENSIONS = ['jpg', 'gif', 'png', 'svg', 'svg'];\n\n// Make a data URL from public.photo\nexport function makeImageUrl(photo) {\n return (photo && photo.type && photo.data) ?\n 'data:image/' + photo.type + ';base64,' + photo.data : null;\n}\n\n// Calculate linear dimensions for scaling image down to fit under a certain size.\n// Returns an object which contains destination sizes, source sizes, and offsets\n// into source (when making square images).\nexport function fitImageSize(width, height, maxWidth, maxHeight, forceSquare) {\n // Sanitize input\n width = width | 0;\n height = height | 0;\n maxWidth = maxWidth | 0;\n maxHeight = maxHeight | 0;\n\n if (width <= 0 || height <= 0 || maxWidth <= 0 || maxHeight <= 0) {\n return null;\n }\n\n if (forceSquare) {\n maxWidth = maxHeight = Math.min(maxWidth, maxHeight);\n }\n\n let scale = Math.min(\n Math.min(width, maxWidth) / width,\n Math.min(height, maxHeight) / height\n );\n\n let size = {\n dstWidth: (width * scale) | 0,\n dstHeight: (height * scale) | 0,\n };\n\n if (forceSquare) {\n // Also calculate parameters for making the image square.\n size.dstWidth = size.dstHeight = Math.min(size.dstWidth, size.dstHeight);\n size.srcWidth = size.srcHeight = Math.min(width, height);\n size.xoffset = ((width - size.srcWidth) / 2) | 0;\n size.yoffset = ((height - size.srcWidth) / 2) | 0;\n } else {\n size.xoffset = size.yoffset = 0;\n size.srcWidth = width;\n size.srcHeight = height;\n }\n return size;\n}\n\n// Ensure file's extension matches mime content type\nexport function fileNameForMime(fname, mime) {\n var idx = SUPPORTED_IMAGE_FORMATS.indexOf(mime);\n var ext = MIME_EXTENSIONS[idx];\n\n var at = fname.lastIndexOf('.');\n if (at >= 0) {\n fname = fname.substring(0, at);\n }\n return fname + '.' + ext;\n}\n\n// Convert uploaded image into a base64-encoded string possibly scaling\n// linear dimensions or constraining to a square.\nexport function imageFileScaledToBase64(file, width, height, forceSquare, onSuccess, onError) {\n var img = new Image();\n img.crossOrigin = 'Anonymous';\n img.onerror = function(err) {\n onError(\"Image format unrecognized\");\n }\n img.onload = function() {\n var dim = fitImageSize(this.width, this.height, width, height, forceSquare);\n if (!dim) {\n onError(\"Invalid image\");\n return;\n }\n var canvas = document.createElement('canvas');\n canvas.width = dim.dstWidth;\n canvas.height = dim.dstHeight;\n var ctx = canvas.getContext('2d');\n ctx.imageSmoothingEnabled = true;\n ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight,\n 0, 0, dim.dstWidth, dim.dstHeight);\n\n var mime = (this.width != dim.dstWidth ||\n this.height != dim.dstHeight ||\n SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) ? 'image/jpeg' : file.type;\n var imageBits = canvas.toDataURL(mime);\n var parts = imageBits.split(',');\n // Get actual image type: 'data:image/png;base64,'\n mime = getMimeType(parts[0]);\n if (!mime) {\n onError(\"Unsupported image format\");\n return;\n }\n // Ensure the image is not too large\n var quality = 0.78;\n if (base64DecodedLen(imageBits.length) > MAX_INBAND_ATTACHMENT_SIZE) {\n mime = 'image/jpeg';\n }\n if (mime == 'image/jpeg') {\n // Reduce size of the jpeg by reducing image quality\n while (base64DecodedLen(imageBits.length) > MAX_INBAND_ATTACHMENT_SIZE && quality > 0.45) {\n imageBits = canvas.toDataURL(mime, quality);\n quality *= 0.84;\n }\n }\n if (base64DecodedLen(imageBits.length) > MAX_INBAND_ATTACHMENT_SIZE) {\n onError(\"The image size \" + bytesToHumanSize(base64DecodedLen(imageBits.length)) +\n \" exceeds the \" + bytesToHumanSize(MAX_INBAND_ATTACHMENT_SIZE) + \" limit.\", \"err\");\n return;\n }\n canvas = null;\n onSuccess(imageBits.split(',')[1], mime, dim.dstWidth, dim.dstHeight, fileNameForMime(file.name, mime));\n };\n img.src = URL.createObjectURL(file);\n}\n\n// Convert uploaded image file to base64-encoded string without scaling/converting the image\nexport function imageFileToBase64(file, onSuccess, onError) {\n var reader = new FileReader();\n reader.addEventListener('load', function() {\n var parts = reader.result.split(',');\n var mime = getMimeType(parts[0]);\n if (!mime) {\n onError(\"Failed to process image file\");\n return;\n }\n\n // Get image size.\n var img = new Image();\n img.crossOrigin = 'Anonymous';\n img.onload = function() {\n onSuccess(parts[1], mime, this.width, this.height, fileNameForMime(file.name, mime));\n }\n img.onerror = function(err) {\n onError(\"Image format not recognized\");\n }\n img.src = URL.createObjectURL(file);\n }, false);\n reader.readAsDataURL(file);\n}\n\nexport function fileToBase64(file, onSuccess, onError) {\n var reader = new FileReader();\n reader.addEventListener('load', function() {\n onSuccess(file.type, reader.result.split(',')[1], file.name);\n });\n reader.readAsDataURL(file);\n}\n\n// File pasted from the clipboard. It's either an inline image or a file attachment.\n// FIXME: handle large files out of band.\nexport function filePasted(event, onImageSuccess, onAttachmentSuccess, onError) {\n var items = (event.clipboardData || event.originalEvent.clipboardData || {}).items;\n for (var i in items) {\n var item = items[i];\n if (item.kind === 'file') {\n var file = item.getAsFile();\n if (!file) {\n console.log(\"Failed to get file object from pasted file item\", item.kind, item.type);\n continue;\n }\n if (file.type && file.type.split('/')[0] == 'image') {\n // Handle inline image\n if (file.size > MAX_INBAND_ATTACHMENT_SIZE || SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) {\n imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false, onImageSuccess, onError);\n } else {\n imageFileToBase64(file, onImageSuccess, onError);\n }\n } else {\n // Handle file attachment\n fileToBase64(file, onAttachmentSuccess, onError)\n }\n // Indicate that the pasted data contains a file.\n return true;\n }\n }\n // No file found.\n return false;\n}\n\n// Get mime type from data URL header.\nexport function getMimeType(header) {\n var mime = /^data:(image\\/[-+a-z0-9.]+);base64/.exec(header);\n return (mime && mime.length > 1) ? mime[1] : null;\n}\n\n// Given length of a binary object in bytes, calculate the length after\n// base64 encoding.\nexport function base64EncodedLen(n) {\n return Math.floor((n + 2) / 3) * 4;\n}\n\n// Given length of a base64-encoded object, calculate decoded size of the\n// pbject in bytes.\nexport function base64DecodedLen(n) {\n return Math.floor(n / 4) * 3;\n}\n\n// Re-encode string to standard base64 encoding with padding.\n// The string may be base64-URL encoded without the padding.\nexport function base64ReEncode(str) {\n if (str) {\n str = str.replace(/-/g, '+').replace(/_/g, '/');\n try {\n str = btoa(atob(str));\n } catch(err) {\n console.log(\"Failed to base64 re-encode string.\", err);\n str = null;\n }\n }\n return str;\n}\n","import React from 'react';\n\nimport LetterTile from './letter-tile.jsx';\n\nimport { AVATAR_SIZE } from '../config.js';\nimport { imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js';\n\nexport default class AvatarUpload extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n dataUrl: props.avatar\n };\n\n this.handleFileUpload = this.handleFileUpload.bind(this);\n }\n\n componentDidUpdate(prevProps) {\n if (this.props.avatar != prevProps.avatar) {\n this.setState({dataUrl: this.props.avatar})\n }\n }\n\n handleFileUpload(e) {\n imageFileScaledToBase64(e.target.files[0], AVATAR_SIZE, AVATAR_SIZE, true,\n // Success\n (base64bits, mime) => {\n var du = makeImageUrl({data: base64bits, type: mime});\n this.setState({dataUrl: du});\n this.props.onImageChanged(du);\n },\n // Failure\n (err) => {\n this.props.onError(err, 'err');\n });\n // Clear the value so the same file can be uploaded again.\n e.target.value = '';\n }\n\n render() {\n // Randomize id value in case more than one AvatarUpload is shown\n // at the same time.\n var randId = \"file-input-avatar-\" + (Math.random() + '').substr(2);\n return (\n \n {this.props.readOnly || !this.state.dataUrl ?\n null :\n
{e.preventDefault(); this.props.onImageChanged(null);}}>\n clear \n }\n {this.state.dataUrl ?\n
:\n this.props.readOnly && this.props.uid ?\n
\n \n
\n :\n
{AVATAR_SIZE}×{AVATAR_SIZE}
}\n {this.props.readOnly ? null :\n
}\n {this.props.readOnly ? null :\n
\n file_upload \n }\n
\n );\n }\n};\n","import React from 'react';\n\n/* BEGIN CheckBox: styled checkbox */\nexport default class CheckBox extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleChange = this.handleChange.bind(this);\n }\n\n handleChange() {\n this.props.onChange(this.props.name, !this.props.checked);\n }\n\n render() {\n return (\n this.props.onChange ? (\n this.props.checked ?\n check_box :\n check_box_outline_blank \n ) : (\n this.props.checked ?\n check_box :\n check_box_outline_blank \n )\n );\n }\n}\n/* END CheckBox */\n","import React from 'react';\n\nconst icon_mapping = {'muted': 'notifications_off', 'banned': 'block'};\n\n/* Contact's labels: [you], or icons [muted], [blocked] */\nexport default class ContactBadges extends React.PureComponent {\n render() {\n let badges = null;\n if (this.props.badges && this.props.badges.length > 0) {\n badges = [];\n this.props.badges.map(function(b) {\n if (b.icon) {\n badges.push({icon_mapping[b.icon]} );\n } else {\n const style = 'badge' + (b.color ? ' ' + b.color : '');\n badges.push({b.name} );\n }\n });\n return <>{badges}>;\n }\n return null;\n }\n};\n","// The counter of unread messages in the topic.\nimport React from 'react';\n\nexport default class UnreadBadge extends React.PureComponent {\n render() {\n return (this.props.count > 0 ?\n {this.props.count > 9 ? \"9+\" : this.props.count} \n : null);\n }\n};\n","// A single topic or user.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport LetterTile from './letter-tile.jsx';\nimport ContactBadges from './contact-badges.jsx';\nimport UnreadBadge from './unread-badge.jsx';\n\nexport default class Contact extends React.Component {\n constructor(props) {\n super(props);\n\n this.handleClick = this.handleClick.bind(this);\n this.handleContextClick = this.handleContextClick.bind(this);\n }\n\n handleClick(e) {\n e.preventDefault();\n e.stopPropagation();\n if (this.props.onSelected) {\n this.props.onSelected(this.props.item, this.props.index, this.props.now, this.props.acs);\n }\n }\n\n handleContextClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.showContextMenu({ topicName: this.props.item, y: e.pageY, x: e.pageX });\n }\n\n render() {\n let title = this.props.title;\n if (!title) {\n title = ;\n } else if (title.length > 30) {\n // FIXME: this is probably wrong for RTL languages.\n title = title.substring(0, 28) + '...';\n }\n const online = this.props.now ? 'online' : 'offline';\n const avatar = this.props.avatar ? this.props.avatar : true;\n const badges = this.props.badges ? this.props.badges.slice() : [];\n const icon_badges = [];\n if (this.props.acs) {\n if (this.props.showMode) {\n badges.push({name: this.props.acs.getMode(), key: 'mode'});\n }\n if (this.props.acs.isMuted()) {\n icon_badges.push({icon: 'muted'});\n }\n if (!this.props.acs.isJoiner()) {\n icon_badges.push({icon: 'banned'});\n }\n }\n\n return (\n \n \n \n {this.props.showOnline ? :\n (this.props.showCheckmark && this.props.selected ?\n check_circle \n : null)}\n
\n \n
{title} \n \n
\n {this.props.comment ?
{this.props.comment}
: null}\n
\n
\n {this.props.showContextMenu ?\n \n \n expand_more \n \n : null}\n \n );\n }\n};\n","// A panel included into a list of contacts with an action text.\nimport React from 'react';\nimport { injectIntl } from 'react-intl';\n\nclass ContactAction extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleClick = this.handleClick.bind(this);\n }\n\n handleClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.onAction(this.props.action);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n return (\n \n {formatMessage(this.props.title, this.props.values)}
\n \n );\n }\n};\n\nexport default injectIntl(ContactAction);\n","// ContactList: component for showing a list of contacts,\n// such as a list of group members in a group chat.\nimport React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport Contact from './contact.jsx';\nimport ContactAction from './contact-action.jsx';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\nconst messages = defineMessages({\n badge_you: {\n id: 'badge_you',\n defaultMessage: 'you',\n description: 'Badge for indicating the current user'\n },\n badge_owner: {\n id: 'badge_owner',\n defaultMessage: 'owner',\n description: 'Badge for indicating the owner'\n }\n});\n\nclass ContactList extends React.Component {\n render() {\n const {formatMessage} = this.props.intl;\n const showCheckmark = Array.isArray(this.props.topicSelected);\n const contactNodes = [];\n let contactsCount = 0;\n if (this.props.contacts && this.props.contacts.length > 0) {\n this.props.contacts.map((c) => {\n if (c.action) {\n // Action item\n contactNodes.push(\n );\n } else {\n // Normal contact\n const key = c.topic ? c.topic : c.user;\n // If filter function is provided, filter out the items\n // which don't satisfy the condition.\n if (this.props.filterFunc && this.props.filter) {\n let content = [key];\n if (c.private && c.private.comment) {\n content.push(('' + c.private.comment).toLowerCase());\n }\n if (c.public && c.public.fn) {\n content.push(('' + c.public.fn).toLowerCase());\n }\n if (!this.props.filterFunc(this.props.filter, content)) {\n return;\n }\n }\n\n const selected = showCheckmark ?\n (this.props.topicSelected.indexOf(key) > -1) :\n (this.props.topicSelected === key);\n const badges = [];\n if (this.props.showMode) {\n if (key === this.props.myUserId) {\n badges.push({name: formatMessage(messages.badge_you), color: 'green'});\n }\n if (c.acs && c.acs.isOwner()) {\n badges.push({name: formatMessage(messages.badge_owner), color: 'blue'});\n }\n }\n const comment = Array.isArray(c.private) ?\n c.private.join(',') : (c.private ? c.private.comment : null);\n\n contactNodes.push(\n \n );\n contactsCount ++;\n }\n }, this);\n }\n\n return (\n \n {contactsCount == 0 ?\n
\n :\n null\n }\n {contactNodes.length > 0 ?\n
\n :\n null\n }\n
\n );\n }\n};\n\nexport default injectIntl(ContactList);\n","// The X menu to be displayed in title bars.\nimport React from 'react';\n\nexport default class MenuCancel extends React.PureComponent {\n render() {\n return (\n {e.preventDefault(); this.props.onCancel();}}>close \n );\n }\n}\n","import React from 'react';\n\nimport MenuCancel from './menu-cancel.jsx';\n\nexport default class ErrorPanel extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n show: false,\n };\n\n this.hide = this.hide.bind(this);\n }\n\n componentDidUpdate(prevProps) {\n if (prevProps.level !== this.props.level) {\n this.setState({\n show: !(!this.props.level)\n });\n }\n }\n\n hide() {\n this.setState({show: false});\n if (this.props.onClearError) {\n this.props.onClearError();\n }\n }\n\n render() {\n const icons = {err: 'error', warn: 'warning', info: 'info'}\n const level = icons[this.props.level] || '';\n const className = 'info-box ' + level;\n return (\n \n );\n }\n};\n","import React from 'react';\n\nimport LetterTile from './letter-tile.jsx';\n\nexport default class Chip extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n handleCancel(e) {\n e.preventDefault();\n this.props.onCancel(this.props.topic, this.props.index);\n }\n\n render() {\n const title = this.props.title || this.props.topic;\n const className = this.props.invalid ? 'chip invalid' : 'chip';\n return (\n \n {this.props.noAvatar ?\n
:\n
\n \n
\n }\n
{title} \n {this.props.onCancel && !this.props.required ?\n
× \n :
}\n
\n );\n }\n};\n","import React from 'react';\n\nimport Chip from './chip.jsx';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\n/* BEGIN ChipInput: group membership widget */\nexport default class ChipInput extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = ChipInput.deriveStateFromProps(props);\n this.state.input = '';\n this.state.focused = false;\n\n this.handleTextInput = this.handleTextInput.bind(this);\n this.removeChipAt = this.removeChipAt.bind(this);\n this.handleChipCancel = this.handleChipCancel.bind(this);\n this.handleFocusGained = this.handleFocusGained.bind(this);\n this.handleFocusLost = this.handleFocusLost.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n }\n\n static deriveStateFromProps(props) {\n return {\n placeholder: props.chips ? '' : props.prompt,\n sortedChips: ChipInput.sortChips(props.chips, props.staticMembers),\n chipIndex: ChipInput.indexChips(props.chips)\n };\n }\n\n componentDidUpdate(prevProps, prevState) {\n if (prevProps.chips != this.props.chips ||\n prevProps.staticMembers != this.props.staticMembers ||\n prevProps.prompt != this.props.prompt) {\n this.setState(ChipInput.deriveStateFromProps(this.props));\n }\n if (!prevState || this.props.chips.length > prevState.sortedChips.length) {\n this.setState({input: ''});\n }\n }\n\n // Map chip index to user name\n static indexChips(chips) {\n const index = {};\n let count = 0;\n chips.map((item) => {\n index[item.user] = count;\n count ++;\n });\n return index;\n }\n\n // Have non-removable chips appear before all other chips.\n static sortChips(chips, keep) {\n const required = [];\n const normal = [];\n chips.map((item) => {\n if (keep && keep.includes(item.user)) {\n required.push(item);\n } else {\n normal.push(item);\n }\n });\n return required.concat(normal);\n }\n\n handleTextInput(e) {\n this.setState({input: e.target.value});\n if (this.props.filterFunc) {\n this.props.filterFunc(e.target.value);\n }\n }\n\n removeChipAt(idx) {\n const removed = this.state.sortedChips[idx];\n this.props.onChipRemoved(removed.user, this.state.chipIndex[removed.user]);\n }\n\n handleChipCancel(item, idx) {\n this.removeChipAt(idx);\n }\n\n handleFocusGained() {\n this.setState({focused: true});\n }\n\n handleFocusLost() {\n this.setState({focused: false});\n if (this.props.onFocusLost) {\n this.props.onFocusLost(this.state.input);\n }\n }\n\n handleKeyDown(e) {\n if (e.key === 'Backspace') {\n if (this.state.input.length == 0 && this.state.sortedChips.length > 0) {\n const at = this.state.sortedChips.length - 1;\n if (this.state.sortedChips[at].user !== this.props.staticMembers) {\n this.removeChipAt(at);\n }\n }\n } else if (e.key === 'Enter') {\n if (this.props.onEnter) {\n this.props.onEnter(this.state.input);\n }\n } else if (e.key === 'Escape') {\n if (this.props.onCancel) {\n this.props.onCancel();\n }\n }\n }\n\n render() {\n const chips = [];\n let count = 0;\n const staticMembers = this.props.staticMembers || [];\n this.state.sortedChips.map((item) => {\n chips.push(\n \n );\n count++;\n });\n const className = \"chip-input\" + (this.state.focused ? ' focused' : '');\n return (\n \n {chips}\n \n
\n );\n }\n};\n","// GroupMembers: control for managing a list of group members.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport ChipInput from './chip-input.jsx';\nimport ContactList from './contact-list.jsx';\n\nconst messages = defineMessages({\n no_contacts: {\n id: 'no_contacts',\n defaultMessage: 'You have no contacts :-(',\n description: 'Shown in ContactsView when the user has no contacts'\n },\n contacts_not_found: {\n id: 'contacts_not_found_short',\n defaultMessage: \"No contacts match '{query}'\",\n description: 'Shown in ContactsView when search returned no results'\n }\n});\n\nclass GroupManager extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n // Array of topic members\n members: props.members,\n index: GroupManager.indexMembers(props.members),\n staticMembers: GroupManager.staticMembers(props.members, props.keepInitialMembers, props.requiredMember),\n contactFilter: '',\n noContactsMessage: props.intl.formatMessage(messages.no_contacts),\n selectedContacts: GroupManager.selectedContacts(props.members)\n };\n\n this.handleContactSelected = this.handleContactSelected.bind(this);\n this.handleMemberRemoved = this.handleMemberRemoved.bind(this);\n this.handleContactFilter = this.handleContactFilter.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n static indexMembers(members) {\n let index = {};\n members.map((m) => {\n index[m.user] = {delta: 0, present: true}; // Delta: 0 unchanged, +1 added, -1 removed\n });\n return index;\n }\n\n static staticMembers(members, keepInitial, requiredMember) {\n let stat = [];\n members.map((m) => {\n if (keepInitial || m.user == requiredMember) {\n stat.push(m.user);\n }\n });\n return stat;\n }\n\n static selectedContacts(members) {\n let sel = [];\n members.map((m) => {\n sel.push(m.user);\n });\n return sel;\n }\n\n handleContactSelected(userId, index) {\n let status = this.state.index[userId];\n if (status) {\n if (status.present) {\n // Prevent duplicate members\n return;\n }\n status.delta += 1;\n status.present = true;\n } else {\n status = {delta: 1, present: true};\n }\n\n let m = this.state.members.slice();\n m.push(this.props.contacts[index]);\n\n const sel = GroupManager.selectedContacts(m);\n\n const i = this.state.index;\n i[userId] = status;\n\n this.setState({members: m, index: i, selectedContacts: sel});\n }\n\n handleMemberRemoved(userId, index) {\n const status = this.state.index[userId];\n if (!status || !status.present) {\n return;\n }\n status.present = false;\n status.delta -= 1;\n\n let m = this.state.members.slice();\n m.splice(index, 1);\n\n const sel = GroupManager.selectedContacts(m);\n\n const i = this.state.index;\n i[userId] = status;\n\n this.setState({members: m, index: i, selectedContacts: sel});\n }\n\n handleContactFilter(val) {\n const {formatMessage} = this.props.intl;\n const msg = !val ?\n formatMessage(messages.no_contacts) :\n formatMessage(messages.contacts_not_found, {query: val});\n\n this.setState({contactFilter: val, noContactsMessage: msg});\n }\n\n static doContactFiltering(filter, values) {\n if (filter) {\n for (let i=0; i= 0) {\n return true;\n }\n }\n return false;\n }\n return true;\n }\n\n handleSubmit() {\n var instance = this;\n var members = [];\n var added = [];\n var removed = [];\n\n var keys = Object.keys(this.state.index);\n keys.map(function(k) {\n if (instance.state.index[k].present) {\n members.push(k);\n }\n\n if (instance.state.index[k].delta > 0) {\n added.push(k);\n } else if (instance.state.index[k].delta < 0) {\n removed.push(k);\n }\n });\n this.props.onSubmit(members, added, removed);\n }\n\n handleCancel() {\n this.props.onCancel();\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n return (\n \n
\n \n \n \n
\n
\n \n
\n
\n \n \n \n
\n
\n
\n \n \n \n \n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(GroupManager);\n","// Password with a visiblity toggle.\nimport React from 'react';\n\nexport default class VisiblePassword extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n value: this.props.value,\n visible: false\n };\n\n this.handleVisibility = this.handleVisibility.bind(this);\n this.handeTextChange = this.handeTextChange.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n }\n\n handeTextChange(e) {\n this.setState({value: e.target.value});\n if (this.props.onChange) {\n this.props.onChange(e);\n }\n }\n\n handleVisibility(e) {\n e.preventDefault();\n this.setState({visible: !this.state.visible});\n }\n\n handleKeyDown(e) {\n if (e.keyCode == 27) {\n // Escape pressed\n this.setState({value: this.props.value, visible: false});\n if (this.props.onFinished) {\n this.props.onFinished();\n }\n } else if (e.keyCode == 13) {\n // Enter pressed\n this.handleEditingFinished();\n }\n }\n\n handleEditingFinished(e) {\n if (e) {\n let currentTarget = e.currentTarget;\n setTimeout(() => {\n if (!currentTarget.contains(document.activeElement)) {\n if (this.props.onFinished) {\n this.props.onFinished(this.state.value);\n }\n }\n }, 0);\n } else if (this.props.onFinished) {\n this.props.onFinished(this.state.value.trim());\n }\n }\n\n render() {\n return (\n \n \n \n \n {this.state.visible ? 'visibility' : 'visibility_off'}\n \n \n
\n );\n }\n}\n","// In-place text editor. Shows text with an icon which toggles it into an input field.\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport VisiblePassword from './visible-password.jsx';\n\nexport default class InPlaceEdit extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n active: props.active,\n initialValue: props.value || '',\n value: props.value || ''\n };\n\n this.handeTextChange = this.handeTextChange.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n this.handleStartEditing = this.handleStartEditing.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n this.handlePasswordFinished = this.handlePasswordFinished.bind(this);\n }\n\n componentDidUpdate(prevProps, prevState) {\n // If text has changed while in read mode, update text and discard changes.\n // Ignore update if in edit mode.\n const newValue = this.props.value || '';\n if (prevState.initialValue != newValue && !prevState.active) {\n this.setState({\n initialValue: newValue,\n value: newValue\n });\n }\n }\n\n handeTextChange(e) {\n this.setState({value: e.target.value});\n }\n\n handleKeyDown(e) {\n if (e.keyCode === 27) {\n // Escape pressed\n this.setState({value: this.props.value, active: false});\n } else if (e.keyCode === 13) {\n // Enter pressed\n this.handleEditingFinished(e);\n }\n }\n\n handleStartEditing() {\n if (!this.props.readOnly) {\n ReactDOM.findDOMNode(this).focus();\n this.setState({active: true});\n }\n }\n\n handleEditingFinished(event) {\n if (this.props.required && !event.target.checkValidity()) {\n // Empty input\n this.setState({value: this.props.value, active: false});\n return;\n }\n this.setState({active: false});\n let value = this.state.value.trim();\n if ((value || this.props.value) && (value !== this.props.value)) {\n this.props.onFinished(value);\n }\n }\n\n handlePasswordFinished(value) {\n this.setState({active: false});\n if (value && (value !== this.props.value)) {\n this.props.onFinished(value);\n }\n }\n\n render() {\n if (this.state.active) {\n var fieldType = this.props.type || 'text';\n } else {\n var spanText = this.props.type == 'password' ? '••••••••' : this.state.value;\n var spanClass = 'in-place-edit' +\n (this.props.readOnly ? ' disabled' : '');\n if (!spanText) {\n spanText = this.props.placeholder;\n spanClass += ' placeholder';\n }\n if (spanText.length > 20) {\n // FIXME: this is wrong for RTL languages.\n spanText = spanText.substring(0, 19) + '...';\n }\n }\n return (\n this.state.active ?\n (fieldType == 'password' ?\n \n :\n \n )\n :\n \n {spanText} \n \n );\n }\n};\n","// Toggle [Title text >] -> [Title text v]\n\nimport React from 'react';\n\nexport default class MoreButton extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n open: props.open\n };\n this.handleToggle = this.handleToggle.bind(this);\n }\n\n handleToggle() {\n const open = !this.state.open;\n this.setState({open: open});\n if (this.props.onToggle) {\n this.props.onToggle(open);\n }\n }\n\n render() {\n return ({this.props.title}...\n {this.state.open ? expand_more :\n chevron_right }\n );\n }\n}\n","// PermissionsEditor: Component for editing permissions\n// \nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport CheckBox from './checkbox.jsx';\nimport Contact from './contact.jsx';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\n// Translatable permission names.\nconst messages = defineMessages({\n 'joiner': {\n id: 'permission_join',\n defaultMessage: \"Join ({val})\",\n description: 'Name of J permission'\n },\n 'reader': {\n id: 'permission_read',\n defaultMessage: \"Read ({val})\",\n description: 'Name of R permission'\n },\n 'writer': {\n id: 'permission_write',\n defaultMessage: \"Write ({val})\",\n description: 'Name of W permission'\n },\n 'preser': {\n id: 'permission_pres',\n defaultMessage: \"Get notified ({val})\",\n description: 'Name of P permission'\n },\n 'approver': {\n id: 'permission_admin',\n defaultMessage: \"Approve ({val})\",\n description: 'Name of A permission'\n },\n 'sharer': {\n id: 'permission_share',\n defaultMessage: \"Share ({val})\",\n description: 'Name of S permission'\n },\n 'deleter': {\n id: 'permission_delete',\n defaultMessage: \"Delete ({val})\",\n description: 'Name of D permission'\n },\n 'owner': {\n id: 'permission_owner',\n defaultMessage: \"Owner ({val})\",\n description: 'Name of O permission'\n }\n});\n\nclass PermissionsEditor extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n mode: (props.mode || '').replace('N', '')\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n handleChange(val) {\n let mode = this.state.mode;\n let idx = mode.indexOf(val);\n if (idx == -1) {\n mode += val;\n } else {\n mode = mode.replace(val, '');\n }\n this.setState({mode: mode});\n }\n\n handleSubmit() {\n // Normalize string, otherwise cannot check if mode has changed.\n var mode = (this.state.mode || 'N').split('').sort().join('');\n var before = (this.props.mode || 'N').split('').sort().join('')\n if (mode !== before) {\n this.props.onSubmit(mode);\n } else {\n this.props.onCancel();\n }\n }\n\n handleCancel() {\n this.props.onCancel();\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const all = 'JRWPASDO';\n const names = {\n 'J': formatMessage(messages.joiner, {val: 'J'}),\n 'R': formatMessage(messages.reader, {val: 'R'}),\n 'W': formatMessage(messages.writer, {val: 'W'}),\n 'P': formatMessage(messages.preser, {val: 'P'}),\n 'A': formatMessage(messages.approver, {val: 'A'}),\n 'S': formatMessage(messages.sharer, {val: 'S'}),\n 'D': formatMessage(messages.deleter, {val: 'D'}),\n 'O': formatMessage(messages.owner, {val: 'O'})\n };\n\n let skip = this.props.skip || '';\n let mode = this.state.mode;\n let compare = (this.props.compare || '').replace('N', '');\n let items = [];\n for (let i=0; i= 0 && mode.indexOf(c) < 0) {\n // Permission is marked as inactive: hide unchecked permissions, disable checked permissions\n continue;\n }\n items.push(\n \n {names[c]} \n {skip.indexOf(c) < 0 ?\n = 0)} onChange={this.handleChange}/>\n :\n = 0)} />\n } {this.props.compare ? \n = 0)}/>\n : null}\n \n );\n }\n\n return (\n \n {this.props.userTitle ?\n
: null}\n
\n
\n {this.props.compare ?\n \n {this.props.modeTitle} \n {this.props.compareTitle} \n :\n null}\n \n {items}\n
\n
\n
\n \n \n \n \n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(PermissionsEditor);\n","// Odds and ends\n\n// Make shortcut icon appear with a green dot + show unread count in title.\nexport function updateFavicon(count) {\n const oldIcon = document.getElementById('shortcut-icon');\n const head = document.head || document.getElementsByTagName('head')[0];\n const newIcon = document.createElement('link');\n newIcon.type = 'image/png';\n newIcon.id = 'shortcut-icon';\n newIcon.rel = 'shortcut icon';\n newIcon.href = 'img/logo32x32' + (count > 0 ? 'a' : '') + '.png';\n if (oldIcon) {\n head.removeChild(oldIcon);\n }\n head.appendChild(newIcon);\n\n document.title = (count > 0 ? '('+count+') ' : '') + 'Tinode';\n}\n\n// Create VCard which represents topic 'public' info\nexport function vcard(fn, imageDataUrl) {\n let card = null;\n\n if ((fn && fn.trim()) || imageDataUrl) {\n card = {};\n if (fn) {\n card.fn = fn.trim();\n }\n if (imageDataUrl) {\n const dataStart = imageDataUrl.indexOf(',');\n card.photo = dataStart >= 0 ? {\n data: imageDataUrl.substring(dataStart+1),\n type: 'jpg'\n } : imageDataUrl;\n }\n }\n return card;\n}\n\n// Deep-shallow compare two arrays: shallow compare each element.\nexport function arrayEqual(a, b) {\n if (a === b) {\n return true;\n }\n\n if (!Array.isArray(a) || !Array.isArray(b)) {\n return false;\n }\n\n // Compare lengths first.\n if (a.length != b.length) {\n return false;\n }\n // Order of elements is ignored.\n a.sort();\n b.sort();\n for (let i = 0, l = a.length; i < l; i++) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n}\n\n// Checks (loosely) if the given string is a phone. If so, returns the phone number in a format\n// as close to E.164 as possible.\nexport function asPhone(val) {\n val = val.trim();\n if (/^(?:\\+?(\\d{1,3}))?[- (.]*(\\d{3})[- ).]*(\\d{3})[- .]*(\\d{2})[- .]*(\\d{2})?$/.test(val)) {\n return val.replace(/[- ().]*/, '');\n }\n return null;\n}\n\n// Checks (loosely) if the given string is an email. If so returns the email.\nexport function asEmail(val) {\n val = val.trim();\n if (/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$/.test(val)) {\n return val;\n }\n return null;\n}\n\n// Checks if URL is a relative url, i.e. has no 'scheme://', including the case of missing scheme '//'.\n// The scheme is expected to be RFC-compliant, e.g. [a-z][a-z0-9+.-]*\n// example.html - ok\n// https:example.com - not ok.\n// http:/example.com - not ok.\n// ' ↲ https://example.com' - not ok. (↲ means carriage return)\nexport function isUrlRelative(url) {\n return !/^\\s*([a-z][a-z0-9+.-]*:|\\/\\/)/im.test(url);\n}\n\n// Ensure URL does not present an XSS risk. Optional allowedSchemes may contain an array of\n// strings with permitted URL schemes, such as ['ftp', 'ftps']; otherwise accept http and https only.\nexport function sanitizeUrl(url, allowedSchemes) {\n if (!url) {\n return null;\n }\n\n // Strip control characters and whitespace. They are not valid URL characters anyway.\n url = url.replace(/[^\\x20-\\x7E]/gmi, '').trim();\n\n // Relative URLs are safe.\n if (!/^([a-z][a-z0-9+.-]*:|\\/\\/)/i.test(url)) {\n return url;\n }\n // Blob URLs are safe.\n if (/^blob:http/.test(url)) {\n return url;\n }\n\n // Absolute URL. Accept only safe schemes, or no scheme.\n const schemes = Array.isArray(allowedSchemes) ? allowedSchemes.join('|') : 'http|https';\n const re = new RegExp('^((' + schemes + '):|//)', 'i');\n if (!re.test(url)) {\n return null;\n }\n\n return url;\n}\n\n// Ensure URL is suitable for field: the URL must be a relative URL or\n// have http:, https:, or data: scheme. In case of data: scheme, the URL must start with\n// a 'data:image/XXXX;base64,'.\nexport function sanitizeImageUrl(url) {\n if (!url) {\n return null;\n }\n\n const sanitizedUrl = sanitizeUrl(url);\n if (sanitizedUrl) {\n return sanitizedUrl;\n }\n\n // Is this a data: URL of an image?\n if (/data:image\\/[a-z0-9.-]+;base64,/i.test(url.trim())) {\n return url;\n }\n\n return null;\n}\n","// TagManager: edit topic or user tags.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport ChipInput from './chip-input.jsx';\n\nimport { MIN_TAG_LENGTH } from '../config.js';\nimport { arrayEqual } from '../lib/utils.js';\n\nexport default class TagManager extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n tags: this.props.tags,\n tagInput: '',\n activated: this.props.activated\n };\n\n this.handleTagInput = this.handleTagInput.bind(this);\n this.handleAddTag = this.handleAddTag.bind(this);\n this.handleRemoveTag = this.handleRemoveTag.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n static getDerivedStateFromProps(nextProps, prevState) {\n if (!arrayEqual(nextProps.tags, prevState.tags) && !prevState.activated) {\n return {tags: nextProps.tags};\n }\n return null;\n }\n\n handleTagInput(text) {\n this.setState({tagInput: text});\n if (text.length > 0) {\n const last = text[text.length-1];\n if (text[0] == '\"') {\n // This is a quoted string.\n if (text.length > 1 && last == '\"') {\n this.handleAddTag(text.substring(1, text.length-1));\n }\n } else if (last == ',' || last == ' ' || last == ';' || last == '\"') {\n // User entered ',', ' ' or ';'\n this.handleAddTag(text.substring(0, text.length-1).trim());\n }\n }\n }\n\n handleAddTag(tag) {\n if (tag.length > 0) {\n const tags = this.state.tags.slice(0);\n tags.push(tag);\n\n this.setState({tags: tags, tagInput: ''});\n if (this.props.onTagsChanged) {\n this.props.onTagsChanged(tags);\n }\n return tags;\n }\n return this.state.tags;\n }\n\n handleRemoveTag(tag, index) {\n const tags = this.state.tags.slice(0);\n tags.splice(index, 1);\n this.setState({tags: tags});\n if (this.props.onTagsChanged) {\n this.props.onTagsChanged(tags);\n }\n }\n\n handleSubmit() {\n // Add unprocessed input to tags, submit the list.\n this.props.onSubmit(this.handleAddTag(this.state.tagInput.trim()));\n this.setState({activated: false, tags: this.props.tags});\n }\n\n handleCancel() {\n this.setState({activated: false, tagInput: '', tags: this.props.tags});\n if (this.props.onCancel) {\n this.props.onCancel();\n }\n }\n\n render() {\n let tags = [];\n if (this.state.activated) {\n this.state.tags.map((tag) => {\n tags.push({user: tag, invalid: (tag.length < MIN_TAG_LENGTH)});\n });\n } else {\n this.state.tags.map((tag) => {\n tags.push({tag} );\n });\n if (tags.length == 0) {\n tags = (\n \n \n \n );\n }\n }\n return (\n \n
\n {this.props.title} \n
\n {this.state.activated ?\n
\n
{\n (add_tags_prompt) => \n } \n {this.props.onSubmit || this.props.onCancel ?\n
\n \n \n \n \n \n \n
\n : null}\n
\n :\n
\n }\n
\n );\n }\n};\n","// InfoView: panel with topic/user info.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport CheckBox from '../widgets/checkbox.jsx';\nimport ContactList from '../widgets/contact-list.jsx';\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport GroupManager from '../widgets/group-manager.jsx';\nimport InPlaceEdit from '../widgets/in-place-edit.jsx';\nimport MenuCancel from '../widgets/menu-cancel.jsx';\nimport MoreButton from '../widgets/more-button.jsx';\nimport PermissionsEditor from '../widgets/permissions-editor.jsx';\nimport TagManager from '../widgets/tag-manager.jsx';\n\nimport { MAX_TITLE_LENGTH, NO_ACCESS_MODE } from '../config.js';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\nimport { arrayEqual, vcard } from '../lib/utils.js';\n\nconst messages = defineMessages({\n requested: {\n id: 'requested_permissions',\n defaultMessage: 'Requested',\n description: 'Title for permissions'\n },\n granted: {\n id: 'granted_permissions',\n defaultMessage: 'Granted',\n description: 'Title for permissions'\n },\n edit_permissions: {\n id: 'menu_item_edit_permissions',\n defaultMessage: 'Edit permissions',\n description: 'Menu item [Edit permissions]'\n },\n other_user: {\n id: 'label_other_user',\n defaultMessage: 'Other',\n description: 'Label for the other user when the user is unnamed'\n },\n clear_messages: {\n id: 'action_clear_messages',\n defaultMessage: 'Clear Messages',\n description: 'Flat button [Clear Messages] (soft-delete messages)'\n },\n clear_messages_warning: {\n id: 'clear_messages_warning',\n defaultMessage: 'Are you sure you want to clear all messages? It cannot be undone.',\n description: 'Alert dialog warning when deleting all messages.'\n },\n delete_messages: {\n id: 'action_delete_messages',\n defaultMessage: 'Clear Messages for All',\n description: 'Flat button [Clear for All] (hard-delete all messages)'\n },\n delete_messages_warning: {\n id: 'delete_messages_warning',\n defaultMessage: 'Are you sure you want to delete all messages for everyone? It cannot be undone.',\n description: 'Alert dialog warning when hard-deleting all messages.'\n },\n leave_chat: {\n id: 'action_leave_chat',\n defaultMessage: 'Leave Conversation',\n description: 'Flat button [Leave Conversation]'\n },\n leave_chat_warning: {\n id: 'leave_chat_warning',\n defaultMessage: 'Are you sure you want to leave this conversation?',\n description: 'Alert dialog warning when unsubscribing from a chat.'\n },\n block_contact: {\n id: 'action_block_contact',\n defaultMessage: \"Block Contact\",\n description: \"Flat button [Block Contact]\"\n },\n block_contact_warning: {\n id: 'block_contact_warning',\n defaultMessage: 'Are you sure you want to block this contact?',\n description: 'Alert dialog warning when blocking a contact.'\n },\n report_chat: {\n id: 'action_report_chat',\n defaultMessage: 'Report Conversation',\n description: 'Flat button [Report Group]'\n },\n report_chat_warning: {\n id: 'report_chat_warning',\n defaultMessage: 'Are you sure you want to block and report this conversation?',\n description: 'Alert dialog warning when reporting a conversation for abuse'\n },\n});\n\nclass InfoView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n topic: null,\n owner: false,\n admin: false,\n sharer: false,\n deleter: false,\n muted: false,\n address: null,\n groupTopic: undefined,\n fullName: undefined,\n avatar: null,\n private: null,\n selectedContact: null,\n access: null,\n modeGiven: null,\n modeWant: null,\n modeGiven2: null, // P2P topic, the other user mode given\n modeWant2: null, // P2P topic, the other user mode want\n auth: null,\n anon: null,\n contactList: [],\n tags: [],\n showMemberPanel: false,\n showPermissionEditorFor: undefined,\n moreInfoExpanded: false,\n previousMetaDesc: undefined,\n previousSubsUpdated: undefined,\n previousTagsUpdated: undefined\n };\n\n this.resetSubs = this.resetSubs.bind(this);\n this.resetDesc = this.resetDesc.bind(this);\n this.onMetaDesc = this.onMetaDesc.bind(this);\n this.onSubsUpdated = this.onSubsUpdated.bind(this);\n this.onTagsUpdated = this.onTagsUpdated.bind(this);\n this.handleFullNameUpdate = this.handleFullNameUpdate.bind(this);\n this.handlePrivateUpdate = this.handlePrivateUpdate.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleMuted = this.handleMuted.bind(this);\n this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);\n this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);\n this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);\n this.handleShowAddMembers = this.handleShowAddMembers.bind(this);\n this.handleHideAddMembers = this.handleHideAddMembers.bind(this);\n this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);\n this.handleDeleteMessages = this.handleDeleteMessages.bind(this);\n this.handleLeave = this.handleLeave.bind(this);\n this.handleBlock = this.handleBlock.bind(this);\n this.handleReport = this.handleReport.bind(this);\n this.handleMemberSelected = this.handleMemberSelected.bind(this);\n this.handleMoreInfo = this.handleMoreInfo.bind(this);\n this.handleTagsUpdated = this.handleTagsUpdated.bind(this);\n this.handleContextMenu = this.handleContextMenu.bind(this);\n }\n\n // No need to separately handle component mount.\n componentDidUpdate(props) {\n const topic = this.props.tinode.getTopic(props.topic);\n if (!topic) {\n return;\n }\n\n if (this.onMetaDesc != topic.onMetaDesc) {\n this.previousMetaDesc = topic.onMetaDesc;\n topic.onMetaDesc = this.onMetaDesc;\n\n this.previousSubsUpdated = topic.onSubsUpdated;\n topic.onSubsUpdated = this.onSubsUpdated;\n\n if (topic.getType() == 'grp') {\n this.previousTagsUpdated = topic.onTagsUpdated;\n topic.onTagsUpdated = this.onTagsUpdated;\n } else {\n this.previousTagsUpdated = undefined;\n }\n }\n\n if (this.state.topic != props.topic) {\n this.setState({topic: props.topic});\n this.resetDesc(topic, props);\n this.resetSubs(topic, props);\n }\n }\n\n componentWillUnmount() {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.setState({topic: null});\n topic.onMetaDesc = this.previousMetaDesc;\n topic.onSubsUpdated = this.previousSubsUpdated;\n topic.onTagsUpdated = this.previousTagsUpdated;\n }\n\n resetSubs(topic, props) {\n const newState = {contactList: []};\n if (topic.getType() == 'p2p') {\n // Fetch the other party in the p2p conversation.\n // Topic may not be ready yet, so check if user is found.\n const user2 = topic.subscriber(props.topic);\n if (user2) {\n newState.modeGiven2 = user2.acs.getGiven();\n newState.modeWant2 = user2.acs.getWant();\n } else {\n newState.modeGiven2 = NO_ACCESS_MODE;\n newState.modeWant2 = NO_ACCESS_MODE;\n }\n } else {\n topic.subscribers((sub) => {\n newState.contactList.push(sub);\n }, this);\n }\n\n this.setState(newState);\n }\n\n resetDesc(topic, props) {\n const defacs = topic.getDefaultAccess() || {};\n const acs = topic.getAccessMode();\n\n this.setState({\n owner: acs && acs.isOwner(),\n admin: acs && acs.isAdmin(),\n sharer: acs && acs.isSharer(),\n deleter: acs && acs.isDeleter(),\n muted: acs && acs.isMuted(),\n\n fullName: topic.public ? topic.public.fn : undefined,\n avatar: makeImageUrl(topic.public ? topic.public.photo : null),\n private: topic.private ? topic.private.comment : null,\n address: topic.name,\n groupTopic: (topic.getType() == 'grp'),\n showMemberPanel: false,\n access: acs ? acs.getMode() : undefined,\n modeGiven: acs ? acs.getGiven() : undefined,\n modeWant: acs ? acs.getWant() : undefined,\n auth: defacs.auth,\n anon: defacs.anon\n });\n\n if (topic.getType() == 'grp' && acs && acs.isOwner()) {\n // Requesting tags: owner is editing the topic.\n topic.getMeta(topic.startMetaQuery().withTags().build());\n }\n }\n\n onMetaDesc(desc) {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.resetDesc(topic, this.props);\n\n if (this.previousMetaDesc && this.previousMetaDesc != this.onMetaDesc) {\n this.previousMetaDesc(desc);\n }\n }\n\n onSubsUpdated(subs) {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.resetSubs(topic, this.props);\n\n if (this.previousSubsUpdated && this.previousSubsUpdated != this.onSubsUpdated) {\n this.previousSubsUpdated(subs);\n }\n }\n\n onTagsUpdated(tags) {\n this.setState({tags: tags});\n\n if (this.previousTagsUpdated && this.previousTagsUpdated != this.onTagsUpdated) {\n this.previousTagsUpdated();\n }\n }\n\n handleFullNameUpdate(fn) {\n fn = fn.trim().substring(0, MAX_TITLE_LENGTH);\n if (this.state.fullName !== fn) {\n this.setState({fullName: fn});\n this.props.onTopicDescUpdate(this.props.topic, vcard(fn, null), null);\n }\n }\n\n handlePrivateUpdate(comment) {\n comment = comment.trim().substring(0, MAX_TITLE_LENGTH);\n if (this.state.private !== comment) {\n this.setState({private: comment});\n this.props.onTopicDescUpdate(this.props.topic, null, comment || Tinode.DEL_CHAR);\n }\n }\n\n handleImageChanged(img) {\n this.setState({avatar: img});\n this.props.onTopicDescUpdate(this.props.topic, vcard(null, img || Tinode.DEL_CHAR), null);\n }\n\n handleMuted(ignored, checked) {\n this.setState({muted: checked});\n this.props.onChangePermissions(this.props.topic, checked ? '-P' : '+P');\n }\n\n handlePermissionsChanged(perm) {\n switch (this.state.showPermissionEditorFor) {\n case 'auth':\n this.props.onTopicDescUpdate(this.props.topic, null, null, {auth: perm});\n break;\n case 'anon':\n this.props.onTopicDescUpdate(this.props.topic, null, null, {anon: perm});\n break;\n case 'mode':\n case 'want':\n this.props.onChangePermissions(this.props.topic, perm);\n break;\n case 'given':\n this.props.onChangePermissions(this.props.topic, perm, this.props.topic);\n break;\n case 'user':\n this.props.onChangePermissions(this.props.topic, perm, this.state.userPermissionsEdited);\n break;\n }\n\n this.setState({showPermissionEditorFor: undefined});\n }\n\n handleLaunchPermissionsEditor(which, uid) {\n const {formatMessage} = this.props.intl;\n let toEdit, toCompare, toSkip, titleEdit, titleCompare, userTitle, userAvatar;\n switch (which) {\n case 'mode':\n toEdit = this.state.access;\n break;\n case 'want':\n toEdit = this.state.modeWant;\n toCompare = this.state.modeGiven;\n toSkip = this.state.groupTopic ? 'O' : 'ASDO';\n titleEdit = formatMessage(messages.requested);\n titleCompare = formatMessage(messages.granted);\n break;\n case 'given':\n toEdit = this.state.modeGiven2;\n toCompare = this.state.modeWant2;\n toSkip = this.state.groupTopic ? (this.state.owner ? '' : 'O') : 'ASDO';\n titleEdit = formatMessage(messages.granted);\n titleCompare = formatMessage(messages.requested);\n break;\n case 'auth':\n toEdit = this.state.auth;\n toSkip = 'O';\n break;\n case 'anon':\n toEdit = this.state.anon;\n toSkip = 'O';\n break;\n case 'user': {\n let topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n var user = topic.subscriber(uid);\n if (!user || !user.acs) {\n return;\n }\n toEdit = user.acs.getGiven();\n toCompare = user.acs.getWant();\n toSkip = this.state.owner ? '' : 'O';\n titleEdit = formatMessage(messages.granted);\n titleCompare = formatMessage(messages.requested);\n if (user.public) {\n userTitle = user.public.fn;\n userAvatar = user.public.photo;\n }\n break;\n }\n default:\n console.log(\"Unknown permission editing mode '\" + which + \"'\");\n break;\n }\n this.setState({\n showPermissionEditorFor: which,\n userPermissionsEdited: uid,\n userPermissionsTitle: userTitle,\n userPermissionsAvatar: userAvatar,\n editedPermissions: toEdit,\n immutablePermissions: toCompare,\n editedPermissionsTitle: titleEdit,\n immutablePermissionsTitle: titleCompare,\n editedPermissionsSkipped: toSkip,\n });\n }\n\n handleHidePermissionsEditor() {\n this.setState({showPermissionEditorFor: undefined});\n }\n\n handleShowAddMembers(e) {\n e.preventDefault();\n this.props.onInitFind();\n this.setState({showMemberPanel: true});\n }\n\n handleHideAddMembers() {\n this.setState({showMemberPanel: false});\n }\n\n handleMemberUpdateRequest(members, added, removed) {\n this.props.onMemberUpdateRequest(this.props.topic, added, removed);\n this.setState({showMemberPanel: false});\n }\n\n handleDeleteMessages(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(this.state.deleter ? messages.delete_messages : messages.clear_messages), // title\n formatMessage(this.state.deleter ? messages.delete_messages_warning : messages.clear_messages_warning), // content\n (() => { this.props.onDeleteMessages(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleLeave(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.leave_chat), // title\n formatMessage(messages.leave_chat_warning), // content\n (() => { this.props.onLeaveTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleBlock(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.block_contact), // title\n formatMessage(messages.block_contact_warning), // content\n (() => { this.props.onBlockTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleReport(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.report_chat), // title\n formatMessage(messages.report_chat_warning), // content\n (() => { this.props.onReportTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleMemberSelected(uid) {\n this.setState({selectedContact: uid});\n }\n\n handleMoreInfo(open) {\n this.setState({moreInfoExpanded: open});\n }\n\n handleTagsUpdated(tags) {\n if (!arrayEqual(this.state.tags.slice(0), tags.slice(0))) {\n this.props.onTopicTagsUpdate(this.props.topic, tags);\n }\n }\n\n handleContextMenu(params) {\n const {formatMessage} = this.props.intl;\n const instance = this;\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n const user = topic.subscriber(params.topicName);\n if (!user || !user.acs) {\n return;\n }\n\n const menuItems = [\n {title: formatMessage(messages.edit_permissions), handler: function() {\n instance.handleLaunchPermissionsEditor('user', params.topicName);\n }},\n 'member_delete',\n user.acs.isMuted() ? 'member_unmute' : 'member_mute',\n user.acs.isJoiner() ? 'member_block' : 'member_unblock'\n ];\n this.props.showContextMenu({\n topicName: this.props.topic,\n x: params.x,\n y: params.y,\n user: params.topicName}, menuItems);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n\n return (\n \n
\n {this.props.displayMobile ?\n
: null}\n {this.state.showMemberPanel ?\n
\n :\n this.state.showPermissionEditorFor ?\n
\n :\n
\n
\n
\n
\n \n
\n
Unknown}\n readOnly={!this.state.owner}\n value={this.state.fullName}\n required={true}\n onFinished={this.handleFullNameUpdate} />
\n
\n \n \n \n
\n
\n {\n (private_placeholder) => \n } \n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n
\n
{\n (more) => \n } \n {this.state.moreInfoExpanded ?\n
\n
\n \n {this.state.address} \n
\n {this.state.groupTopic ?\n
\n \n \n \n \n {this.state.access}\n \n
\n :\n
\n
\n \n \n \n
\n
\n
\n \n {this.state.access}\n
\n
{this.state.fullName ? this.state.fullName : formatMessage(messages.other_user)}:\n \n {this.state.modeGiven2}\n \n
\n
\n
\n }\n {this.state.sharer && (this.state.auth || this.state.anon) ?\n
\n
\n \n \n \n
\n
\n
Auth: {this.state.admin ?\n \n {this.state.auth}\n \n :\n {this.state.auth} \n }\n
\n
Anon: {this.state.admin ?\n \n {this.state.anon}\n \n :\n {this.state.anon} \n }\n
\n
\n
\n :\n null\n }\n
\n :\n null\n }\n
\n
\n {this.state.owner ?\n <>\n
{\n (tags) => \n } \n
\n >\n :\n null\n }\n
\n {this.state.groupTopic ?\n <>\n
\n
\n
\n \n \n \n
\n
\n
{\n (no_members) => \n } \n
\n >\n :\n null\n }\n
\n }\n
\n );\n }\n};\n\nexport default injectIntl(InfoView);\n","// File uload/download progress indicator with a cancel inside.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class FileProgress extends React.PureComponent {\n render() {\n return (\n \n );\n }\n}\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport FileProgress from './file-progress.jsx';\nimport { bytesToHumanSize } from '../lib/strformat.js';\nimport { isUrlRelative, sanitizeUrl } from '../lib/utils.js';\n\nexport default class Attachment extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n downloader: null,\n progress: 0\n };\n\n this.downloadFile = this.downloadFile.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n downloadFile(url, filename, mimetype) {\n var downloader = this.props.tinode.getLargeFileHelper();\n this.setState({downloader: downloader});\n downloader.download(url, filename, mimetype, (loaded) => {\n this.setState({progress: loaded / this.props.size});\n }).then(() => {\n this.setState({downloader: null, progress: 0});\n }).catch((err) => {\n if (err) {\n this.props.onError(\"Error downloading file: \" + err.message, 'err');\n }\n this.setState({downloader: null, progress: 0});\n });\n }\n\n handleCancel() {\n if (this.props.uploader) {\n this.props.onCancelUpload();\n } else if (this.state.downloader) {\n this.state.downloader.cancel();\n }\n }\n\n render() {\n let filename = this.props.filename || 'file_attachment';\n if (filename.length > 36) {\n filename = filename.substr(0, 16) + '...' + filename.substr(-16);\n }\n let size = this.props.size > 0 ?\n ({bytesToHumanSize(this.props.size)}) :\n null;\n\n // Detect if the download URL is relative or absolute.\n // If the URL is relative use LargeFileHelper to attach authentication\n // credentials to the request.\n let url, helperFunc;\n if (!this.props.uploader && !this.state.downloader && isUrlRelative(this.props.downloadUrl)) {\n // Relative URL. Use download helper.\n url = '#';\n helperFunc = (e) => {\n e.preventDefault();\n this.downloadFile(this.props.downloadUrl, this.props.filename, this.props.mimetype);\n };\n } else {\n url = sanitizeUrl(this.props.downloadUrl) || 'about:blank';\n helperFunc = null;\n }\n return (\n \n
insert_drive_file
\n
\n
{filename} {size}
\n {this.props.uploader || this.state.downloader ?\n
\n :\n
\n }\n
\n
\n );\n }\n};\n","// Received/read indicator.\nimport React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport { shortDateFormat } from '../lib/strformat.js';\n\nconst messages = defineMessages({\n 'sending': {\n 'id': 'message_sending',\n 'defaultMessage': 'sending...',\n 'description': 'Message being sent, in place of time stamp'\n },\n 'failed': {\n 'id': 'message_sending_failed',\n 'defaultMessage': 'failed',\n 'description': 'Failed to send message, in place of time stamp'\n }\n});\n\nclass ReceivedMarker extends React.PureComponent {\n render() {\n const {formatMessage} = this.props.intl;\n let timestamp;\n if (this.props.received <= Tinode.MESSAGE_STATUS_SENDING) {\n timestamp = formatMessage(messages.sending);\n } else if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {\n timestamp = formatMessage(messages.failed);\n } else {\n timestamp = shortDateFormat(this.props.timestamp, this.props.intl.locale);\n }\n\n let marker = null;\n if (this.props.received <= Tinode.MESSAGE_STATUS_SENDING) {\n marker = (access_time ); // watch face\n } else if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {\n marker = (warning ); // yellow icon /!\\\n } else if (this.props.received == Tinode.MESSAGE_STATUS_SENT) {\n marker = (done ); // checkmark\n } else if (this.props.received == Tinode.MESSAGE_STATUS_RECEIVED) {\n marker = (done_all ); // double checkmark\n } else if (this.props.received == Tinode.MESSAGE_STATUS_READ) {\n marker = (done_all ); // blue double checkmark\n }\n\n return (\n \n {timestamp}{'\\u00a0'}{marker}\n \n );\n }\n};\n\nexport default injectIntl(ReceivedMarker);\n","// Single message, sent or received.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\nimport { Drafty } from 'tinode-sdk'\n\nimport Attachment from './attachment.jsx';\nimport LetterTile from './letter-tile.jsx';\nimport ReceivedMarker from './received-marker.jsx'\nimport { sanitizeImageUrl, sanitizeUrl } from '../lib/utils.js';\n\nexport default class ChatMessage extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n progress: 0\n };\n\n if (props.uploader) {\n props.uploader.onProgress = this.handleProgress.bind(this);\n }\n\n this.handleImagePreview = this.handleImagePreview.bind(this);\n this.handleFormButtonClick = this.handleFormButtonClick.bind(this);\n this.handleContextClick = this.handleContextClick.bind(this);\n this.handleCancelUpload = this.handleCancelUpload.bind(this);\n }\n\n handleImagePreview(e) {\n e.preventDefault();\n this.props.onImagePreview({\n url: e.target.src,\n filename: e.target.title,\n width: e.target.dataset.width,\n height: e.target.dataset.height,\n size: e.target.dataset.size,\n type: e.target.dataset.mime\n });\n }\n\n handleFormButtonClick(e) {\n e.preventDefault();\n const data = {\n seq: this.props.seq\n };\n data.resp = {};\n if (e.target.dataset.name) {\n data.resp[e.target.dataset.name] = e.target.dataset.val ? e.target.dataset.val :\n e.target.dataset.val === undefined ? 1 : '' + e.target.dataset.val;\n }\n if (e.target.dataset.act == 'url') {\n data.ref = sanitizeUrl(e.target.dataset.ref) || 'about:blank';\n }\n const text = e.target.dataset.title || 'unknown';\n this.props.onFormResponse(e.target.dataset.act, text, data);\n }\n\n handleContextClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.showContextMenu({ seq: this.props.seq, y: e.pageY, x: e.pageX });\n }\n\n handleProgress(ratio) {\n this.setState({progress: ratio});\n }\n\n handleCancelUpload() {\n this.props.uploader.cancel();\n }\n\n render() {\n const sideClass = this.props.deleted ? 'center' :\n (this.props.sequence + ' ' + (this.props.response ? 'left' : 'right'));\n const bubbleClass = (this.props.sequence == 'single' || this.props.sequence == 'last') ? 'bubble tip' : 'bubble';\n const avatar = this.props.deleted ? null : (this.props.userAvatar || true);\n const fullDisplay = (this.props.userFrom && this.props.response &&\n (this.props.sequence == 'single' || this.props.sequence == 'last'));\n\n let content = this.props.content;\n const attachments = [];\n if (this.props.mimeType == Drafty.getContentType()) {\n Drafty.attachments(content, function(att, i) {\n if (att.mime == 'application/json') {\n // Don't show json objects as attachments.\n // They are not meant for users.\n return;\n }\n attachments.push( );\n }, this);\n content = React.createElement('span', null, Drafty.format(content, draftyFormatter, this));\n } else if (this.props.deleted) {\n // Message represents a range of deleted messages.\n content = <>block \n \n >\n } else if (typeof content != 'string') {\n content = <>error_outline \n \n >\n }\n\n return (\n \n {this.props.userFrom && this.props.response ?\n \n {fullDisplay ?\n :\n null}\n
:\n null}\n \n
\n
\n {content}\n {attachments}\n {this.props.timestamp ?\n \n : null}\n
\n {this.props.deleted ?\n null :\n
\n \n expand_more \n \n \n }\n
\n {fullDisplay ?\n
\n {\n (notFound) => {return this.props.userName || {notFound} }\n } \n
\n : null\n }\n
\n \n );\n }\n};\n\n// Convert Drafty object to a tree of React elements.\nimport { BROKEN_IMAGE_SIZE, REM_SIZE } from '../config.js';\nimport { fitImageSize } from '../lib/blob-helpers.js';\n\nfunction draftyFormatter(style, data, values, key) {\n let el = Drafty.tagName(style);\n if (el) {\n let attr = Drafty.attrValue(style, data) || {};\n attr.key = key;\n switch (style) {\n case 'IM':\n // Additional processing for images\n if (data) {\n attr.className = 'inline-image';\n let dim = fitImageSize(data.width, data.height,\n Math.min(this.props.viewportWidth - REM_SIZE * 4, REM_SIZE * 36), REM_SIZE * 24, false);\n dim = dim || {dstWidth: BROKEN_IMAGE_SIZE, dstHeight: BROKEN_IMAGE_SIZE};\n attr.style = { width: dim.dstWidth + 'px', height: dim.dstHeight + 'px' };\n attr.src = sanitizeImageUrl(attr.src);\n if (attr.src) {\n attr.onClick = this.handleImagePreview;\n attr.className += ' image-clickable';\n } else {\n attr.src = 'img/broken_image.png';\n }\n }\n break;\n case 'BN':\n // Button\n attr.onClick = this.handleFormButtonClick;\n let inner = React.Children.map(values, (child) => {\n return typeof child == 'string' ? child : undefined;\n });\n if (!inner || inner.length == 0) {\n inner = [attr.name]\n }\n // Get text which will be sent back when the button is clicked.\n attr['data-title'] = inner.join('');\n break;\n case 'FM':\n // Form\n attr.className = 'bot-form';\n break;\n case 'FE':\n // Form element formatting is dependent on element content.\n break;\n }\n return React.createElement(el, attr, values);\n } else {\n return values;\n }\n};\n","// GroupSubs: a list of group subscribers currently online.\n// Show in the topic title bar\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport { MAX_ONLINE_IN_TOPIC } from '../config.js'\n\nimport LetterTile from './letter-tile.jsx';\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\nexport default class GroupSubs extends React.Component {\n constructor(props) {\n super(props);\n }\n\n render() {\n const usersOnline = [];\n const totalCount = (this.props.subscribers || []).length;\n const countToShow = Math.min(MAX_ONLINE_IN_TOPIC, totalCount);\n\n (this.props.subscribers || []).some((sub) => {\n usersOnline.push(\n \n \n
\n );\n return usersOnline.length == countToShow;\n });\n\n return (\n {usersOnline} {totalCount > countToShow ?\n \n \n : null}\n
\n );\n }\n};\n","// Send message form.\nimport React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\nimport { Drafty } from 'tinode-sdk';\n\nimport { KEYPRESS_DELAY, MAX_EXTERN_ATTACHMENT_SIZE, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';\nimport { filePasted, fileToBase64 } from '../lib/blob-helpers.js';\nimport { bytesToHumanSize } from '../lib/strformat.js';\n\nconst messages = defineMessages({\n 'messaging_disabled': {\n id: 'messaging_disabled_prompt',\n defaultMessage: 'Messaging disabled',\n description: 'Prompt in SendMessage in read-only topic'\n },\n 'type_new_message': {\n id: 'new_message_prompt',\n defaultMessage: 'New message',\n description: 'Prompt in SendMessage in read-only topic'\n },\n 'file_attachment_too_large': {\n id: 'file_attachment_too_large',\n defaultMessage: 'The file size {size} exceeds the {limit} limit.',\n description: 'Error message when attachment is too large'\n },\n 'cannot_initiate_upload': {\n id: 'cannot_initiate_file_upload',\n defaultMessage: 'Cannot initiate file upload.',\n description: 'Generic error messagewhen attachment fails'\n },\n});\n\nclass SendMessage extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n message: '',\n // Make initial keypress time as if it happened 5001 milliseconds in the past.\n keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1\n };\n\n this.handlePasteEvent = this.handlePasteEvent.bind(this);\n this.handleAttachImage = this.handleAttachImage.bind(this);\n this.handleAttachFile = this.handleAttachFile.bind(this);\n this.handleSend = this.handleSend.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleMessageTyping = this.handleMessageTyping.bind(this);\n }\n\n componentDidMount() {\n this.messageEditArea.addEventListener('paste', this.handlePasteEvent, false);\n }\n\n componentWillUnmount() {\n this.messageEditArea.removeEventListener('paste', this.handlePasteEvent, false);\n }\n\n componentDidUpdate() {\n this.messageEditArea.focus();\n }\n\n handlePasteEvent(e) {\n if (this.props.disabled) {\n return;\n }\n // FIXME: handle large files too.\n if (filePasted(e,\n (bits, mime, width, height, fname) => {\n this.props.onAttachImage(mime, bits, width, height, fname);\n },\n (mime, bits, fname) => {\n this.props.onAttachFile(mime, bits, fname);\n },\n this.props.onError)) {\n\n // If a file was pasted, don't paste base64 data into input field.\n e.preventDefault();\n }\n }\n\n handleAttachImage(e) {\n if (e.target.files && e.target.files.length > 0) {\n this.props.onAttachImage(e.target.files[0]);\n }\n // Clear the value so the same file can be uploaded again.\n e.target.value = '';\n }\n\n handleAttachFile(e) {\n const {formatMessage} = this.props.intl;\n if (e.target.files && e.target.files.length > 0) {\n const file = e.target.files[0];\n if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) {\n // Too large.\n this.props.onError(formatMessage(messages.file_attachment_too_large,\n {size: bytesToHumanSize(file.size), limit: bytesToHumanSize(MAX_EXTERN_ATTACHMENT_SIZE)}), 'err');\n } else if (file.size > MAX_INBAND_ATTACHMENT_SIZE) {\n // Too large to send inband - uploading out of band and sending as a link.\n const uploader = this.props.tinode.getLargeFileHelper();\n if (!uploader) {\n this.props.onError(formatMessage(messages.cannot_initiate_upload));\n return;\n }\n // Pass data and the uploader to the TinodeWeb.\n this.props.onAttachFile(file.type, null, file.name, file.size, uploadCompletionPromise, uploader);\n } else {\n // Small enough to send inband.\n fileToBase64(file,\n (mime, bits, fname) => {\n this.props.onAttachFile(mime, bits, fname);\n },\n this.props.onError\n );\n }\n }\n // Clear the value so the same file can be uploaded again.\n e.target.value = '';\n }\n\n handleSend(e) {\n e.preventDefault();\n const message = this.state.message.trim();\n if (message) {\n this.props.onSendMessage(this.state.message.trim());\n this.setState({message: ''});\n }\n }\n\n /* Send on Enter key */\n handleKeyPress(e) {\n // Remove this if you don't want Enter to trigger send\n if (e.key === 'Enter') {\n // Have Shift-Enter insert a line break instead\n if (!e.shiftKey) {\n e.preventDefault();\n e.stopPropagation();\n\n this.handleSend(e);\n }\n }\n }\n\n handleMessageTyping(e) {\n const newState = {message: e.target.value};\n const now = new Date().getTime();\n if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (topic.isSubscribed()) {\n topic.noteKeyPress();\n }\n newState.keypressTimestamp = now;\n }\n this.setState(newState);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const prompt = this.props.disabled ?\n formatMessage(messages.messaging_disabled) :\n formatMessage(messages.type_new_message);\n return (\n \n );\n }\n};\n\nexport default injectIntl(SendMessage);\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\nimport SendMessage from '../widgets/send-message.jsx';\n\nimport { REM_SIZE } from '../config.js';\nimport { fitImageSize } from '../lib/blob-helpers.js';\nimport { bytesToHumanSize } from '../lib/strformat.js';\n\nexport default class ImagePreview extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n width: 0,\n height: 0\n };\n }\n\n assignWidth(node) {\n if (node && !this.state.width) {\n const bounds = node.getBoundingClientRect();\n this.setState({\n width: bounds.width | 0,\n height: bounds.height | 0\n });\n }\n }\n\n render() {\n if (!this.props.content) {\n return null;\n }\n\n const dim = fitImageSize(this.props.content.width, this.props.content.height,\n this.state.width, this.state.height, false);\n const size = dim ? { width: dim.dstWidth + 'px', height: dim.dstHeight + 'px' } :\n ((this.props.content.width > this.props.content.height) ? {width: '100%'} : {height: '100%'});\n size.maxWidth = '100%';\n size.maxHeight = '100%';\n\n let filename = this.props.content.filename;\n // Averate font aspect ratio is ~0.5; File name takes 1/3 of the viewport width.\n const maxlength = Math.max(((this.state.width / REM_SIZE / 1.5) | 0) - 2, 12);\n if (filename.length > maxlength) {\n filename = filename.slice(0, maxlength/2 - 1) + '...' + filename.slice(1 - maxlength/2);\n }\n const width = this.props.content.width || '-';\n const height = this.props.content.height || '-';\n return (\n \n
\n
this.assignWidth(node)}>\n
\n
\n {this.props.onSendMessage ?\n
\n :\n }\n
\n );\n }\n};\n","// A single topic or user.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class Invitation extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleButtonAction = this.handleButtonAction.bind(this);\n }\n\n handleButtonAction(evt, data) {\n evt.preventDefault();\n this.props.onAction(data);\n }\n\n render() {\n return (\n \n
\n \n
\n
\n { this.handleButtonAction(event, \"accept\"); }}>\n \n \n { this.handleButtonAction(event, \"delete\"); }}>\n \n \n { this.handleButtonAction(event, \"block\"); }}>\n \n \n
\n
\n );\n }\n};\n","import React from 'react';\n\nexport default class LoadSpinner extends React.PureComponent {\n render() {\n return (this.props.show ?\n : null);\n }\n}\n","/* This is just a static page to display when no conversation is selected. */\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport { APP_NAME } from '../config.js';\n\nexport default class LogoView extends React.PureComponent {\n render() {\n var version = APP_NAME + ' (' + Tinode.getLibrary() + ')';\n return (\n \n
\n
\n \n Tinode Web \n \n
{version}
\n
{this.props.serverVersion} ({this.props.serverAddress})
\n
\n
\n );\n }\n};\n","import React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\nconst Drafty = Tinode.Drafty;\n\nimport ChatMessage from '../widgets/chat-message.jsx';\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport GroupSubs from '../widgets/group-subs.jsx';\nimport ImagePreview from '../widgets/image-preview.jsx';\nimport Invitation from '../widgets/Invitation.jsx';\nimport LetterTile from '../widgets/letter-tile.jsx';\nimport LoadSpinner from '../widgets/load-spinner.jsx';\nimport LogoView from './logo-view.jsx';\nimport SendMessage from '../widgets/send-message.jsx';\n\nimport { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';\nimport { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64, imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js';\nimport { shortDateFormat } from '../lib/strformat.js';\n\nconst messages = defineMessages({\n online_now: {\n id: 'online_now',\n defaultMessage: 'online now',\n description: 'Indicator that the user or topic is currently online',\n },\n last_seen: {\n id: 'last_seen_timestamp',\n defaultMessage: 'Last seen',\n description: 'Label for the timestamp of when the user or topic was last online'\n },\n 'not_found': {\n id: 'title_not_found',\n defaultMessage: 'Not found',\n description: 'Title shown when topic is not found'\n }\n});\n\n// Checks if the access permissions are granted but not yet accepted.\nfunction isUnconfirmed(acs) {\n if (acs) {\n const ex = acs.getExcessive() || '';\n return acs.isJoiner('given') && (ex.includes('R') || ex.includes('W'));\n }\n return false;\n}\n\nfunction isPeerRestricted(acs) {\n if (acs) {\n const ms = acs.getMissing() || '';\n return acs.isJoiner('want') && (ms.includes('R') || ms.includes('W'));\n }\n return false;\n}\n\nclass MessagesView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = MessagesView.getDerivedStateFromProps(props, {});\n\n // this.propsChange = this.propsChange.bind(this);\n this.leave = this.leave.bind(this);\n this.handleScrollReference = this.handleScrollReference.bind(this);\n this.handleScrollEvent = this.handleScrollEvent.bind(this);\n this.handleDescChange = this.handleDescChange.bind(this);\n this.handleSubsUpdated = this.handleSubsUpdated.bind(this);\n this.handleNewMessage = this.handleNewMessage.bind(this);\n this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);\n this.handleInfoReceipt = this.handleInfoReceipt.bind(this);\n this.handleImagePreview = this.handleImagePreview.bind(this);\n this.handleCloseImagePreview = this.handleCloseImagePreview.bind(this);\n this.handleFormResponse = this.handleFormResponse.bind(this);\n this.handleContextClick = this.handleContextClick.bind(this);\n this.handleShowContextMenuMessage = this.handleShowContextMenuMessage.bind(this);\n this.handleNewChatAcceptance = this.handleNewChatAcceptance.bind(this);\n this.handleEnablePeer = this.handleEnablePeer.bind(this);\n this.handleAttachFile = this.handleAttachFile.bind(this);\n this.handleAttachImage = this.handleAttachImage.bind(this);\n this.handleNewImagePreview = this.handleNewImagePreview.bind(this);\n }\n\n componentDidMount() {\n // this.propsChange(this.props, this.state);\n if (this.messagesScroller) {\n this.messagesScroller.addEventListener('scroll', this.handleScrollEvent);\n }\n }\n\n componentWillUnmount() {\n if (this.messagesScroller) {\n this.messagesScroller.removeEventListener('scroll', this.handleScrollEvent);\n }\n }\n\n // Scroll last message into view on component update e.g. on message received\n // or vertical shrinking.\n componentDidUpdate(prevProps, prevState) {\n if (this.messagesScroller) {\n if (prevState.topic != this.state.topic || prevState.messages.length != this.state.messages.length) {\n this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition;\n } else if (prevProps.viewportHeight > this.props.viewportHeight) {\n this.messagesScroller.scrollTop += prevProps.viewportHeight - this.props.viewportHeight;\n }\n }\n\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (this.state.topic != prevState.topic) {\n if (prevState.topic && !Tinode.isNewGroupTopicName(prevState.topic)) {\n this.leave(prevState.topic);\n this.props.readTimerHandler(null);\n }\n\n if (topic) {\n topic.onData = this.handleNewMessage;\n topic.onAllMessagesReceived = this.handleAllMessagesReceived;\n topic.onInfo = this.handleInfoReceipt;\n topic.onMetaDesc = this.handleDescChange;\n topic.onSubsUpdated = this.handleSubsUpdated;\n topic.onPres = this.handleSubsUpdated;\n }\n }\n\n if (topic && !topic.isSubscribed() && this.props.ready &&\n ((this.state.topic != prevState.topic) || !prevProps.ready)) {\n // Is this a new topic?\n const newTopic = (this.props.newTopicParams && this.props.newTopicParams._topicName == this.props.topic);\n\n // Don't request the tags. They are useless unless the user\n // is the owner and is editing the topic.\n let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub();\n if (this.state.isReader || newTopic) {\n // If reading is either permitted or we don't know because it's a new topic. Ask for messages.\n getQuery = getQuery.withLaterData(MESSAGES_PAGE);\n if (this.state.isReader) {\n getQuery = getQuery.withLaterDel();\n }\n // And show \"loading\" spinner.\n this.setState({ fetchingMessages: true });\n }\n const setQuery = newTopic ? this.props.newTopicParams : undefined;\n topic.subscribe(getQuery.build(), setQuery)\n .then((ctrl) => {\n if (this.state.topic != ctrl.topic) {\n this.setState({topic: ctrl.topic});\n }\n this.props.onNewTopicCreated(this.props.topic, ctrl.topic);\n // If there are unsent messages, try sending them now.\n topic.queuedMessages((pub) => {\n if (!pub._sending && topic.isSubscribed()) {\n topic.publishMessage(pub);\n }\n });\n })\n .catch((err) => {\n console.log(\"Failed subscription to\", this.state.topic);\n this.props.onError(err.message, 'err');\n const blankState = MessagesView.getDerivedStateFromProps({}, {});\n blankState.title = this.props.intl.formatMessage(messages.not_found);\n this.setState(blankState);\n });\n }\n }\n\n static getDerivedStateFromProps(nextProps, prevState) {\n let nextState = {};\n if (!nextProps.topic) {\n // Default state: no topic.\n nextState = {\n messages: [],\n onlineSubs: [],\n topic: null,\n title: '',\n avatar: null,\n imagePreview: null,\n typingIndicator: false,\n scrollPosition: 0,\n fetchingMessages: false,\n peerMessagingDisabled: false\n };\n } else if (nextProps.topic != prevState.topic) {\n const topic = nextProps.tinode.getTopic(nextProps.topic);\n nextState = {\n topic: nextProps.topic,\n imagePreview: null,\n typingIndicator: false,\n scrollPosition: 0,\n fetchingMessages: false\n };\n\n if (topic) {\n // Topic exists.\n const msgs = [];\n const subs = [];\n\n if (nextProps.connected) {\n topic.subscribers((sub) => {\n if (sub.online && sub.user != nextProps.myUserId) {\n subs.push(sub);\n }\n });\n }\n\n topic.messages(function(msg) {\n if (!msg.deleted) {\n msgs.push(msg);\n }\n });\n\n Object.assign(nextState, {\n messages: msgs,\n onlineSubs: subs\n });\n\n if (topic.public) {\n Object.assign(nextState, {\n title: topic.public.fn,\n avatar: makeImageUrl(topic.public.photo)\n });\n } else {\n Object.assign(nextState, {\n title: '',\n avatar: null\n });\n }\n\n const peer = topic.p2pPeerDesc();\n if (peer) {\n Object.assign(nextState, {\n peerMessagingDisabled: isPeerRestricted(peer.acs)\n });\n } else if (prevState.peerMessagingDisabled) {\n Object.assign(nextState, {\n peerMessagingDisabled: false\n });\n }\n } else {\n // Invalid topic.\n Object.assign(nextState, {\n messages: [],\n onlineSubs: [],\n title: '',\n avatar: null,\n peerMessagingDisabled: false\n });\n }\n }\n\n if (nextProps.acs) {\n if (nextProps.acs.isWriter() != prevState.isWriter) {\n nextState.isWriter = !prevState.isWriter;\n }\n if (nextProps.acs.isReader() != prevState.isReader) {\n nextState.isReader = !prevState.isReader;\n }\n if (!nextProps.acs.isReader('given') != prevState.readingBlocked) {\n nextState.readingBlocked = !prevState.readingBlocked;\n }\n } else {\n if (prevState.isWriter) {\n nextState.isWriter = false;\n }\n if (prevState.isReader) {\n nextState.isReader = false;\n }\n if (!prevState.readingBlocked) {\n prevState.readingBlocked = true;\n }\n }\n\n if (isUnconfirmed(nextProps.acs) == !prevState.unconformed) {\n nextState.unconfirmed = !prevState.unconformed;\n }\n\n // Clear subscribers online when there is no connection.\n if (!nextProps.connected && prevState.onlineSubs && prevState.onlineSubs.length > 0) {\n nextState.onlineSubs = [];\n }\n\n return nextState;\n }\n\n leave(oldTopicName) {\n if (!oldTopicName) {\n return;\n }\n let oldTopic = this.props.tinode.getTopic(oldTopicName);\n if (oldTopic && oldTopic.isSubscribed()) {\n oldTopic.leave(false)\n .catch(() => { /* do nothing here */ })\n .finally(() => {\n // We don't care if the request succeeded or failed.\n // The topic is dead regardless.\n this.setState({fetchingMessages: false});\n oldTopic.onData = undefined;\n oldTopic.onAllMessagesReceived = undefined;\n oldTopic.onInfo = undefined;\n oldTopic.onMetaDesc = undefined;\n oldTopic.onSubsUpdated = undefined;\n oldTopic.onPres = undefined;\n });\n }\n }\n\n handleScrollReference(node) {\n if (node) {\n node.addEventListener('scroll', this.handleScrollEvent);\n this.messagesScroller = node;\n }\n }\n\n // Get older messages\n handleScrollEvent(event) {\n this.setState({scrollPosition: event.target.scrollHeight - event.target.scrollTop});\n if (event.target.scrollTop <= 0) {\n this.setState((prevState, props) => {\n const newState = {};\n if (!prevState.fetchingMessages) {\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (topic && topic.isSubscribed() && topic.msgHasMoreMessages()) {\n newState.fetchingMessages = true;\n topic.getMessagesPage(MESSAGES_PAGE).catch((err) => {\n this.setState({fetchingMessages: false});\n this.props.onError(err.message, 'err');\n });\n }\n }\n return newState;\n });\n }\n }\n\n handleDescChange(desc) {\n if (desc.public) {\n this.setState({\n title: desc.public.fn,\n avatar: makeImageUrl(desc.public.photo)\n });\n } else {\n this.setState({\n title: '',\n avatar: null\n });\n }\n\n if (desc.acs) {\n this.setState({\n isWriter: desc.acs.isWriter(),\n isReader: desc.acs.isReader(),\n readingBlocked: !desc.acs.isReader('given'),\n unconfirmed: isUnconfirmed(desc.acs),\n });\n }\n }\n\n handleSubsUpdated() {\n if (this.state.topic) {\n const subs = [];\n const topic = this.props.tinode.getTopic(this.state.topic);\n topic.subscribers((sub) => {\n if (sub.online && sub.user != this.props.myUserId) {\n subs.push(sub);\n }\n });\n const newState = {onlineSubs: subs};\n const peer = topic.p2pPeerDesc();\n if (peer) {\n Object.assign(newState, {\n peerMessagingDisabled: isPeerRestricted(peer.acs)\n });\n } else if (this.state.peerMessagingDisabled) {\n Object.assign(newState, {\n peerMessagingDisabled: false\n });\n }\n this.setState(newState);\n }\n }\n\n handleNewMessage(msg) {\n // Regenerate messages list\n const topic = this.props.tinode.getTopic(this.state.topic);\n const newState = {messages: []};\n topic.messages((m) => {\n if (!m.deleted) {\n newState.messages.push(m);\n }\n });\n\n // msg could be null if one or more messages were deleted.\n if (msg && !msg.deleted) {\n // If the message is added to the end of the message list,\n // scroll to the bottom.\n if (topic.isNewMessage(msg.seq)) {\n newState.scrollPosition = 0;\n }\n\n // Aknowledge messages except own messages. They are\n // automatically assumed to be read and recived.\n const status = topic.msgStatus(msg);\n if (status >= Tinode.MESSAGE_STATUS_SENT && msg.from != this.props.myUserId) {\n this.props.readTimerHandler(() => {\n topic.noteRead(msg.seq);\n });\n }\n this.props.onData(msg);\n }\n this.setState(newState);\n }\n\n handleAllMessagesReceived(count) {\n this.setState({fetchingMessages: false});\n }\n\n handleInfoReceipt(info) {\n switch (info.what) {\n case 'kp': {\n clearTimeout(this.keyPressTimer);\n var instance = this;\n this.keyPressTimer = setTimeout(function() {\n instance.setState({typingIndicator: false});\n }, KEYPRESS_DELAY + 1000);\n if (!this.state.typingIndicator) {\n this.setState({typingIndicator: true});\n }\n break;\n }\n case 'read':\n case 'recv':\n // Redraw due to changed recv/read status.\n this.forceUpdate();\n break;\n default:\n console.log(\"Other change in topic: \", info.what);\n }\n }\n\n handleImagePreview(content) {\n this.setState({ imagePreview: content });\n }\n\n handleCloseImagePreview() {\n this.setState({ imagePreview: null });\n }\n\n handleFormResponse(action, text, data) {\n if (action == 'pub') {\n this.props.sendMessage(Drafty.attachJSON(Drafty.parse(text), data));\n } else if (action == 'url') {\n const url = new URL(data.ref);\n const params = url.searchParams;\n for (let key in data.resp) {\n if (data.resp.hasOwnProperty(key)) {\n params.set(key, data.resp[key]);\n }\n }\n ['name', 'seq'].map(function(key) {\n if (data[key]) {\n params.set(key, data[key]);\n }\n });\n params.set('uid', this.props.myUserId);\n url.search = params;\n window.open(url, '_blank');\n } else {\n console.log(\"Unknown action in form\", action);\n }\n }\n\n handleContextClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.showContextMenu({ topicName: this.state.topic, y: e.pageY, x: e.pageX });\n }\n\n handleShowContextMenuMessage(params) {\n params.topicName = this.state.topic;\n const menuItems = ['message_delete'];\n const topic = this.props.tinode.getTopic(params.topicName);\n if (topic) {\n const acs = topic.getAccessMode();\n if (acs && acs.isDeleter()) {\n menuItems.push('message_delete_hard');\n }\n }\n this.props.showContextMenu(params, menuItems);\n }\n\n handleNewChatAcceptance(action) {\n this.props.onNewChat(this.state.topic, action);\n }\n\n handleEnablePeer(e) {\n e.preventDefault();\n this.props.onChangePermissions(this.state.topic, DEFAULT_P2P_ACCESS_MODE, this.state.topic);\n }\n\n handleAttachFile(mime, bits, fname, size, uploadCompletionPromise, uploader) {\n if (uploadCompletionPromise) {\n // Format data and initiate upload.\n const msg = Drafty.attachFile(null, mime, null, fname, size, uploadCompletionPromise);\n // Pass data and the uploader to the TinodeWeb.\n this.props.sendMessage(msg, uploadCompletionPromise, uploader);\n } else {\n this.props.sendMessage(Drafty.attachFile(null, mime, bits, fname));\n }\n }\n\n handleNewImagePreview(content) {\n this.setState({ imagePreview: content });\n // this.props.sendMessage(Drafty.insertImage(null, 0, mime, bits, width, height, fname));\n }\n\n handleAttachImage(file) {\n // Check if the uploaded file is indeed an image and if it isn't too large.\n if (file.size > MAX_INBAND_ATTACHMENT_SIZE || SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) {\n // Convert image for size or format.\n imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false,\n // Success\n (bits, mime, width, height, fname) => {\n this.handleNewImagePreview({\n url: URL.createObjectURL(file),\n filename: fname,\n width: width,\n height: height,\n size: bits.length,\n type: mime\n });\n },\n // Failure\n (err) => {\n this.props.onError(err, 'err');\n });\n } else {\n // Image can be uploaded as is. No conversion is needed.\n imageFileToBase64(file,\n // Success\n (bits, mime, width, height, fname) => {\n this.handleNewImagePreview({\n url: URL.createObjectURL(file),\n filename: fname,\n width: width,\n height: height,\n size: bits.length,\n type: mime\n });\n },\n // Failure\n (err) => {\n this.props.onError(err, 'err');\n }\n );\n }\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n\n let component;\n if (this.props.hideSelf) {\n component = null;\n } else if (!this.state.topic) {\n component = (\n \n );\n } else {\n let component2;\n if (this.state.imagePreview) {\n component2 = (\n \n );\n } else {\n const topic = this.props.tinode.getTopic(this.state.topic);\n const groupTopic = topic.getType() == 'grp';\n let messageNodes = [];\n let previousFrom = null;\n let chatBoxClass = null;\n for (let i=0; i \n );\n }\n\n let lastSeen = null;\n const cont = this.props.tinode.getMeTopic().getContact(this.state.topic);\n if (cont && Tinode.topicType(cont.topic) == 'p2p') {\n if (cont.online) {\n lastSeen = formatMessage(messages.online_now);\n } else if (cont.seen) {\n lastSeen = formatMessage(messages.last_seen) + \": \" +\n shortDateFormat(cont.seen.when, this.props.intl.locale);\n // TODO: also handle user agent in c.seen.ua\n }\n }\n const avatar = this.state.avatar || true;\n const online = this.props.online ? 'online' + (this.state.typingIndicator ? ' typing' : '') : 'offline';\n\n component2 = (\n <>\n \n {this.props.displayMobile ?\n \n : null}\n \n \n
\n {!this.state.isReader ?\n
\n {this.state.readingBlocked ?\n
\n \n
\n : null }\n
\n : null }\n
\n {this.state.peerMessagingDisabled && !this.state.unconfirmed ?\n : null}\n {this.state.unconfirmed ?\n \n :\n }\n >\n );\n }\n\n component = {component2}
\n }\n return component;\n }\n};\n\nexport default injectIntl(MessagesView);\n","import React from 'react';\n\nexport default class MenuContacts extends React.PureComponent {\n render() {\n return (\n \n );\n }\n};\n","import React from 'react';\n\nexport default class MenuStart extends React.PureComponent {\n render() {\n return (\n \n );\n }\n};\n","import React from 'react';\n\nimport LetterTile from './letter-tile.jsx';\nimport MenuCancel from './menu-cancel.jsx';\nimport MenuContacts from './menu-contacts.jsx';\nimport MenuStart from './menu-start.jsx';\n\nexport default class SideNavbar extends React.PureComponent {\n render() {\n return (\n \n
\n \n
\n
{this.props.title}
\n {this.props.state === 'login' ?\n
:\n this.props.state === 'contacts' ?\n
:\n null}\n {this.props.onCancel ?\n
: null}\n
\n );\n }\n};\n","/* ContactsView holds all contacts-related stuff */\nimport React from 'react';\nimport { FormattedHTMLMessage, defineMessages } from 'react-intl';\n\nimport ContactList from '../widgets/contact-list.jsx';\n\nimport { updateFavicon } from '../lib/utils.js';\n\nconst messages = defineMessages({\n archived_contacts_title: {\n id: \"archived_contacts\",\n defaultMessage: \"Archived contacts ({count})\",\n description: \"Label for archived chats\"\n }\n});\n\nexport default class ContactsView extends React.Component {\n constructor(props) {\n super(props);\n\n this.handleAction = this.handleAction.bind(this);\n\n this.state = ContactsView.deriveStateFromProps(props);\n }\n\n static deriveStateFromProps(props) {\n const contacts = [];\n let unreadThreads = 0;\n let archivedCount = 0;\n props.chatList.map((c) => {\n if (c.private && c.private.arch) {\n if (props.archive) {\n contacts.push(c);\n } else {\n archivedCount ++;\n }\n } else if (!props.archive) {\n contacts.push(c);\n unreadThreads += c.unread > 0 ? 1 : 0;\n }\n });\n contacts.sort(function(a, b) {\n return (b.touched || 0) - (a.touched || 0);\n });\n\n if (archivedCount > 0) {\n contacts.push({\n action: 'archive',\n title: messages.archived_contacts_title,\n values: {count: archivedCount}\n });\n }\n\n return {\n contactList: contacts,\n unreadThreads: unreadThreads\n };\n }\n\n componentDidUpdate(prevProps, prevState) {\n if (prevProps.chatList != this.props.chatList || prevProps.archive != this.props.archive) {\n const newState = ContactsView.deriveStateFromProps(this.props);\n this.setState(newState);\n if (newState.unreadThreads != prevState.unreadThreads) {\n updateFavicon(newState.unreadThreads);\n }\n }\n }\n\n handleAction(action_ignored) {\n this.props.onShowArchive();\n }\n\n render() {\n return (\n ¯∖_(ツ)_/¯\"\n description=\"HTML message shown in ContactList when no contacts are found\">{\n (no_contacts) => \n }\n );\n }\n};\n","// Helper functions for storing values in localStorage.\n// By default localStorage can store only strings, not objects or other types.\n\nexport default class LocalStorageUtil {\n // Replace old object with the new one.\n static setObject(key, value) {\n localStorage.setItem(key, JSON.stringify(value));\n }\n\n // Get stored object.\n static getObject(key) {\n const value = localStorage.getItem(key);\n return value && JSON.parse(value);\n }\n\n // Partially or wholly update stored object.\n static updateObject(key, value) {\n const oldVal = this.getObject(key);\n this.setObject(key, Object.assign(oldVal || {}, value));\n }\n\n // Just a wrapper.\n static removeItem(key) {\n localStorage.removeItem(key);\n }\n}\n","// Account registration.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport CheckBox from '../widgets/checkbox.jsx';\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport LocalStorageUtil from '../lib/local-storage.js';\nimport { vcard } from '../lib/utils.js';\n\nimport { MAX_TITLE_LENGTH } from '../config.js';\n\nexport default class CreateAccountView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n login: '',\n password: '',\n email: '',\n fn: '', // full/formatted name\n imageDataUrl: null,\n errorCleared: false,\n saveToken: LocalStorageUtil.getObject('keep-logged-in')\n };\n\n this.handleLoginChange = this.handleLoginChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n this.handleEmailChange = this.handleEmailChange.bind(this);\n this.handleFnChange = this.handleFnChange.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleToggleSaveToken = this.handleToggleSaveToken.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleLoginChange(e) {\n this.setState({login: e.target.value});\n }\n\n handlePasswordChange(password) {\n this.setState({password: password});\n }\n\n handleEmailChange(e) {\n this.setState({email: e.target.value})\n }\n\n handleFnChange(e) {\n this.setState({fn: e.target.value});\n }\n\n handleImageChanged(img) {\n this.setState({imageDataUrl: img});\n }\n\n handleToggleSaveToken() {\n LocalStorageUtil.setObject('keep-logged-in', !this.state.saveToken);\n this.setState({saveToken: !this.state.saveToken});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.setState({errorCleared: false});\n this.props.onCreateAccount(\n this.state.login.trim(),\n this.state.password.trim(),\n vcard(this.state.fn.trim().substring(0, MAX_TITLE_LENGTH), this.state.imageDataUrl),\n {'meth': 'email', 'val': this.state.email});\n }\n\n render() {\n let submitClasses = 'blue';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n\n return (\n \n \n
\n {\n (login_prompt) => \n } \n {\n (password_prompt) => \n } \n
\n
\n
\n \n {\n (full_name_prompt) => \n } \n
\n \n {\n (email_prompt) => \n } \n
\n \n \n {\n (stay_logged_in) => {stay_logged_in} \n } \n
\n \n \n \n \n
\n \n );\n }\n};\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport CheckBox from '../widgets/checkbox.jsx';\nimport InPlaceEdit from '../widgets/in-place-edit.jsx';\nimport PermissionsEditor from '../widgets/permissions-editor.jsx';\nimport TagManager from '../widgets/tag-manager.jsx';\n\nimport { LINK_CONTACT_US, LINK_PRIVACY_POLICY, LINK_TERMS_OF_SERVICE, MAX_TITLE_LENGTH } from '../config.js';\nimport { makeImageUrl } from '../lib/blob-helpers.js';\nimport { arrayEqual, asEmail, asPhone, vcard } from '../lib/utils.js';\n\nexport default class EditAccountView extends React.Component {\n constructor(props) {\n super(props);\n\n const me = this.props.tinode.getMeTopic();\n const defacs = me.getDefaultAccess();\n this.state = {\n fullName: me.public ? me.public.fn : undefined,\n avatar: makeImageUrl(me.public ? me.public.photo : null),\n auth: defacs ? defacs.auth : null,\n anon: defacs ? defacs.anon : null,\n tags: me.tags(),\n credentials: me.getCredentials() || [],\n addCredActive: false,\n addCredInvalid: false,\n newCred: '',\n showPermissionEditorFor: undefined,\n previousOnTags: me.onTagsUpdated\n };\n\n this.tnNewTags = this.tnNewTags.bind(this);\n this.tnCredsUpdated = this.tnCredsUpdated.bind(this);\n this.handleFullNameUpdate = this.handleFullNameUpdate.bind(this);\n this.handlePasswordUpdate = this.handlePasswordUpdate.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleCheckboxClick = this.handleCheckboxClick.bind(this);\n this.handleCredChange = this.handleCredChange.bind(this);\n this.handleCredKeyDown = this.handleCredKeyDown.bind(this);\n this.handleCredEntered = this.handleCredEntered.bind(this);\n this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);\n this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);\n this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);\n this.handleTagsUpdated = this.handleTagsUpdated.bind(this);\n }\n\n componentDidMount() {\n const me = this.props.tinode.getMeTopic();\n me.onCredsUpdated = this.tnCredsUpdated;\n me.onTagsUpdated = this.tnNewTags;\n }\n\n componentWillUnmount() {\n const me = this.props.tinode.getMeTopic();\n me.onTagsUpdated = this.state.previousOnTags;\n me.onCredsUpdated = undefined;\n }\n\n tnNewTags(tags) {\n this.setState({tags: tags});\n }\n\n tnCredsUpdated(creds) {\n this.setState({credentials: creds || []});\n }\n\n handleFullNameUpdate(fn) {\n fn = fn.trim().substring(0, MAX_TITLE_LENGTH);\n if (fn) {\n this.setState({fullName: fn});\n this.props.onUpdateAccount(undefined, vcard(fn, null));\n }\n }\n\n handlePasswordUpdate(pwd) {\n this.setState({password: pwd});\n this.props.onUpdateAccount(pwd);\n }\n\n handleImageChanged(img) {\n this.setState({avatar: img});\n this.props.onUpdateAccount(undefined, vcard(null, img || Tinode.DEL_CHAR));\n }\n\n handleCheckboxClick(what, checked) {\n if (what == 'sound') {\n this.props.onToggleMessageSounds(checked);\n } else if (what == 'alert') {\n this.props.onTogglePushNotifications(checked);\n }\n }\n\n handleCredChange(e) {\n this.setState({newCred: e.target.value, addCredInvalid: false});\n }\n\n handleCredKeyDown(e) {\n if (e.keyCode === 27) {\n // Escape pressed\n this.setState({newCred: '', addCredActive: false});\n } else if (e.keyCode === 13) {\n // Enter pressed\n this.handleCredEntered(e);\n }\n }\n\n handleCredEntered(e) {\n let value = this.state.newCred.trim();\n if (!value) {\n this.setState({addCredActive: false, addCredInvalid: false});\n return;\n }\n\n let val = asPhone(value);\n let method;\n if (val) {\n method = 'tel';\n } else {\n val = asEmail(value);\n if (val) {\n method = 'email';\n }\n }\n if (method) {\n this.props.onCredAdd(method, val);\n this.setState({addCredActive: false, newCred: ''});\n } else {\n this.setState({addCredInvalid: true});\n }\n }\n\n handleLaunchPermissionsEditor(which) {\n this.setState({\n showPermissionEditorFor: which,\n editedPermissions: this.state[which]\n });\n }\n\n handleHidePermissionsEditor() {\n this.setState({showPermissionEditorFor: undefined});\n }\n\n handlePermissionsChanged(perm) {\n let defacs = {};\n defacs[this.state.showPermissionEditorFor] = perm;\n this.props.onUpdateAccount(undefined, undefined, defacs);\n\n let newState = {showPermissionEditorFor: undefined};\n newState[this.state.showPermissionEditorFor] = perm;\n this.setState(newState);\n }\n\n handleTagsUpdated(tags) {\n // Check if tags have actually changed.\n if (arrayEqual(this.state.tags.slice(0), tags.slice(0))) {\n return;\n }\n this.props.onUpdateTags(tags);\n }\n\n render() {\n let credentials = [];\n this.state.credentials.map((cred) => {\n credentials.push();\n });\n\n return (\n {this.state.showPermissionEditorFor ?\n \n :\n \n
\n
\n
\n \n
\n
{\n (full_name_placeholder) => \n }
\n
\n \n
\n
\n {\n (password_unchanged) => \n } \n
\n
\n
\n
\n
\n
\n \n \n \n \n
\n
\n \n {this.props.desktopAlertsEnabled ?\n \n :\n \n }\n \n \n
\n
\n
{\n (title_tag_manager) => \n } \n
\n
\n
\n \n \n \n
\n
\n {credentials}\n {this.state.addCredActive ?\n
\n : null}\n
\n
\n
\n
\n
\n
\n \n {this.props.myUserId} \n
\n
\n \n \n \n
\n
\n
Auth: {this.state.auth}
\n
Anon: {this.state.anon}
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n
\n
\n
\n
\n
\n
\n } \n );\n }\n};\n","// Login form.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport CheckBox from '../widgets/checkbox.jsx';\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport LocalStorageUtil from '../lib/local-storage.js';\n\nexport default class LoginView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n login: props.login,\n password: '',\n hostName: props.serverAddress,\n saveToken: LocalStorageUtil.getObject('keep-logged-in')\n };\n this.handleLoginChange = this.handleLoginChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n this.handleToggleSaveToken = this.handleToggleSaveToken.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleLoginChange(e) {\n this.setState({login: e.target.value});\n }\n\n handlePasswordChange(e) {\n this.setState({password: e.target.value});\n }\n\n handleToggleSaveToken() {\n LocalStorageUtil.setObject('keep-logged-in', !this.state.saveToken);\n this.setState({saveToken: !this.state.saveToken});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.props.onLogin(this.state.login.trim(), this.state.password.trim());\n }\n\n render() {\n var submitClasses = 'blue';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n\n return (\n \n \n {\n (login_prompt) => \n }\n \n \n {\n (password_prompt) => \n }\n \n \n \n \n \n \n
\n \n );\n }\n};\n/* END Login */\n","import React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nconst messages = defineMessages({\n invalid_id: {\n id: 'error_invalid_id',\n defaultMessage: 'Invalid ID',\n description: 'Error message'\n }\n});\n\nclass NewTopicById extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n groupId: '',\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleChange(e) {\n this.setState({groupId: e.target.value});\n }\n\n handleKeyPress(e) {\n if (e.key === 'Enter') {\n this.handleSubmit(e);\n }\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.groupId) {\n var name = this.state.groupId.trim();\n if (name.length > 3 && (name.substr(0, 3) == 'usr' || name.substr(0, 3) == 'grp')) {\n this.props.onSubmit(name);\n } else {\n this.props.onError(this.props.intl.formatMessage(messages.invalid_id), 'err');\n }\n }\n }\n\n render() {\n return (\n \n
\n {\n (prompt) => \n } \n
\n
\n \n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(NewTopicById);\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from './avatar-upload.jsx';\nimport TagManager from './tag-manager.jsx';\n\nimport { MAX_TITLE_LENGTH } from '../config.js';\n\nexport default class NewTopicGroup extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n fn: '', // full/formatted name\n private: '',\n imageDataUrl: null,\n tags: []\n };\n\n this.handleFnChange = this.handleFnChange.bind(this);\n this.handlePrivateChange = this.handlePrivateChange.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleTagsChanged = this.handleTagsChanged.bind(this);\n this.handleTagsChanged = this.handleTagsChanged.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleFnChange(e) {\n this.setState({fn: e.target.value});\n }\n\n handlePrivateChange(e) {\n this.setState({private: e.target.value});\n }\n\n handleImageChanged(img) {\n this.setState({imageDataUrl: img});\n }\n\n handleTagsChanged(tags) {\n this.setState({tags: tags});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n\n const fn = this.state.fn.trim().substring(0, MAX_TITLE_LENGTH);\n const comment = this.state.private.trim().substring(0, MAX_TITLE_LENGTH);\n if (fn) {\n this.props.onSubmit(fn, this.state.imageDataUrl, comment, this.state.tags);\n }\n }\n\n render() {\n var submitClasses = 'blue';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n return (\n \n \n {\n (title) => \n } \n \n \n \n \n
\n \n );\n }\n};\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nexport default class SearchContacts extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n edited: false,\n search: ''\n };\n\n this.handleSearchChange = this.handleSearchChange.bind(this);\n this.handleSearch = this.handleSearch.bind(this);\n this.handleClear = this.handleClear.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n }\n\n componentWillUnmount() {\n if (this.state.edited) {\n this.setState({search: '', edited: false});\n this.props.onSearchContacts(Tinode.DEL_CHAR);\n }\n }\n\n handleSearchChange(e) {\n this.setState({search: e.target.value});\n }\n\n handleSearch(e) {\n e.preventDefault();\n var query = this.state.search.trim();\n this.setState({edited: (query.length > 0)});\n this.props.onSearchContacts(query.length > 0 ? query : Tinode.DEL_CHAR);\n }\n\n handleClear(e) {\n e.preventDefault();\n if (this.state.edited) {\n this.props.onSearchContacts(Tinode.DEL_CHAR);\n }\n this.setState({search: '', edited: false});\n }\n\n handleKeyDown(e) {\n if (e.key === 'Enter') {\n this.handleSearch(e);\n } else if (e.key === 'Escape') {\n this.handleClear();\n }\n }\n\n render() {\n return (\n \n
\n
search \n
{\n (search_placeholder) => \n } \n
\n close \n \n
\n
\n );\n }\n};\n","// Create new topic and invite users or send an invite.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport ContactList from '../widgets/contact-list.jsx';\nimport NewTopicById from '../widgets/new-topic-by-id.jsx';\nimport NewTopicGroup from '../widgets/new-topic-group.jsx';\nimport SearchContacts from '../widgets/search-contacts.jsx';\n\nimport HashNavigation from '../lib/navigation.js';\nimport { vcard } from '../lib/utils.js';\n\nconst messages = defineMessages({\n search_for_contacts: {\n id: \"search_for_contacts\",\n defaultMessage: \"Use search to find contacts\",\n description: \"Text shown in contacts view when user entered no search query.\"\n },\n search_no_results: {\n id: \"search_no_results\",\n defaultMessage: \"Search returned no results\",\n description: \"Text shown in contacts view when query returned no results.\"\n }\n});\n\nclass NewTopicView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n tabSelected: 'p2p',\n searchQuery: null\n };\n\n this.handleTabClick = this.handleTabClick.bind(this);\n this.handleSearchContacts = this.handleSearchContacts.bind(this);\n this.handleContactSelected = this.handleContactSelected.bind(this);\n this.handleNewGroupSubmit = this.handleNewGroupSubmit.bind(this);\n this.handleGroupByID = this.handleGroupByID.bind(this);\n }\n\n componentDidMount() {\n this.props.onInitFind();\n }\n\n handleTabClick(e) {\n e.preventDefault();\n HashNavigation.navigateTo(HashNavigation.addUrlParam(window.location.hash, 'tab', e.currentTarget.dataset.id));\n this.setState({tabSelected: e.currentTarget.dataset.id});\n }\n\n handleSearchContacts(query) {\n this.props.onSearchContacts(query);\n this.setState({searchQuery: Tinode.isNullValue(query) ? null : query});\n }\n\n handleContactSelected(sel) {\n if (this.state.tabSelected === 'p2p') {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(sel, undefined);\n }\n }\n\n handleNewGroupSubmit(name, dataUrl, priv, tags) {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(undefined, vcard(name, dataUrl), priv, tags);\n }\n\n handleGroupByID(topicName) {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(topicName);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const no_contacts_placeholder = formatMessage(this.state.searchQuery ?\n messages.search_no_results : messages.search_for_contacts);\n return (\n \n
\n {this.state.tabSelected === 'grp' ?\n
:\n this.state.tabSelected === 'byid' ?\n
:\n
\n \n \n
}\n
\n );\n }\n};\n\nexport default injectIntl(NewTopicView);\n","// A password reset form.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport HashNavigation from '../lib/navigation.js';\n\nexport default class PasswordResetView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n email: '',\n password: ''\n };\n\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleEmailChange = this.handleEmailChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n }\n\n componentDidMount() {\n let parsed = HashNavigation.parseUrlHash(window.location.hash);\n this.setState({token: parsed.params.token, scheme: parsed.params.scheme});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.token) {\n this.props.onReset(this.state.scheme, this.state.password.trim(), this.state.token);\n } else {\n this.props.onRequest('email', this.state.email.trim());\n }\n }\n\n handleEmailChange(e) {\n this.setState({email: e.target.value});\n }\n\n handlePasswordChange(e) {\n this.setState({password: e.target.value});\n }\n\n render() {\n let reset = (this.state.token && this.state.scheme);\n return (\n \n {reset ?\n {\n (placeholder) => \n } \n :\n <>\n \n \n \n {\n (placeholder) => \n } \n >\n }\n \n {reset ?\n :\n \n } \n
\n \n );\n }\n}\n","import React from 'react';\n\nimport { KNOWN_HOSTS } from '../config.js';\n\n/* Combobox for selecting host name */\n\nexport default class HostSelector extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n hostName: props.serverAddress,\n changed: false\n };\n\n this.handleHostNameChange = this.handleHostNameChange.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n }\n\n handleHostNameChange(e) {\n this.setState({hostName: e.target.value, changed: true});\n }\n\n handleEditingFinished() {\n if (this.state.changed) {\n this.setState({changed: false});\n this.props.onServerAddressChange(this.state.hostName.trim());\n }\n }\n\n render() {\n var hostOptions = [];\n for (let key in KNOWN_HOSTS) {\n let item = KNOWN_HOSTS[key];\n hostOptions.push(\n \n );\n }\n return (\n \n \n \n {hostOptions}\n \n
\n );\n }\n}\n","// Tinode config panel.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport HostSelector from '../widgets/host-selector.jsx';\n\nexport default class SettingsView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n transport: props.transport || 'def',\n serverAddress: props.serverAddress,\n };\n\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleTransportSelected = this.handleTransportSelected.bind(this);\n this.handleServerAddressChange = this.handleServerAddressChange.bind(this);\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.props.onUpdate({\n transport: this.state.transport,\n serverAddress: this.state.serverAddress\n });\n }\n\n handleTransportSelected(e) {\n this.setState({transport: e.currentTarget.value});\n }\n\n handleServerAddressChange(name) {\n this.setState({serverAddress: name});\n }\n\n render() {\n const names = {def: \"default\", ws: \"websocket\", lp: \"long polling\"};\n var transportOptions = [];\n var instance = this;\n ['def', 'ws', 'lp'].map(function(item) {\n var id = 'transport-' + item;\n var name = names[item];\n transportOptions.push(\n \n \n {name} \n \n );\n });\n\n return (\n \n \n \n \n \n
\n \n \n \n \n \n
\n \n \n \n \n \n
\n \n );\n }\n};\n","// ValidationView: panel for confirming credentials, like email or phone.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nconst messages = defineMessages({\n phone: {\n id: 'phone_dative',\n defaultMessage: 'phone',\n description: \"Dative case of 'phone', i.e. 'phone' in 'by phone'\",\n },\n email: {\n id: 'email_dative',\n defaultMessage: 'email',\n description: \"Dative case of 'email', i.e. 'email' in 'by email'\",\n }\n});\n\nclass ValidationView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n code: props.credCode || ''\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n handleChange(e) {\n this.setState({code: e.target.value});\n }\n\n handleKeyPress(e) {\n if (e.key === 'Enter') {\n this.handleSubmit(e);\n } else if (e.key == 'Escape') {\n this.handleCancel(e);\n }\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.code && this.state.code.trim()) {\n this.props.onSubmit(this.props.credMethod, this.state.code.trim());\n }\n }\n\n handleCancel(e) {\n e.preventDefault();\n this.props.onCancel();\n }\n\n\n render() {\n const { formatMessage } = this.props.intl;\n const methods = {'email': formatMessage(messages.email), 'tel': formatMessage(messages.phone)};\n let method = methods[this.props.credMethod] || this.props.credMethod;\n return (\n \n
\n \n \n \n
\n
\n {\n (numbers_only) => \n } \n
\n
\n \n \n \n \n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(ValidationView);\n","import React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport LoadSpinner from '../widgets/load-spinner.jsx';\nimport SideNavbar from '../widgets/side-navbar.jsx';\n\nimport ContactsView from './contacts-view.jsx';\nimport CreateAccountView from './create-account-view.jsx';\nimport EditAccountView from './edit-account-view.jsx';\nimport LoginView from './login-view.jsx';\nimport NewTopicView from './new-topic-view.jsx';\nimport PasswordResetView from './password-reset-view.jsx';\nimport SettingsView from './settings-view.jsx';\nimport ValidationView from './validation-view.jsx';\n\n// Panel titles for translation.\nconst messages = defineMessages({\n 'login': {\n id: 'sidepanel_title_login',\n description: 'Sidepanel title for LoginView.',\n defaultMessage: 'Sign In'\n },\n 'register': {\n id: 'sidepanel_title_register',\n description: 'Sidepanel title for CreateAccountView.',\n defaultMessage: 'Create Account'\n },\n 'settings': {\n id: 'sidepanel_title_settings',\n description: 'Sidepanel title for SettingsView.',\n defaultMessage: 'Settings'\n },\n 'edit': {\n id: 'sidepanel_title_edit_account',\n description: 'Sidepanel title for EditAccountView.',\n defaultMessage: 'Edit Account'\n },\n 'newtpk': {\n id: 'sidepanel_title_newtpk',\n description: 'Sidepanel title for NewTopicView.',\n defaultMessage: 'Start New Chat'\n },\n 'cred': {\n id: 'sidepanel_title_cred',\n description: 'Sidepanel title for ValidationView.',\n defaultMessage: 'Confirm Credentials'\n },\n 'reset': {\n id: 'sidepanel_title_reset',\n description: 'Sidepanel title for PasswordResetView.',\n defaultMessage: 'Reset Password'\n },\n 'archive': {\n id: 'sidepanel_title_archive',\n description: 'Sidepanel title for ContactsView-Archive.',\n defaultMessage: 'Archived Chats'\n }\n});\n\nclass SidepanelView extends React.Component {\n constructor(props) {\n super(props);\n\n this.handleLoginRequested = this.handleLoginRequested.bind(this);\n }\n\n handleLoginRequested(login, password) {\n this.props.onLoginRequest(login, password);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const view = this.props.state || (this.props.myUserId ? 'contacts' : 'login');\n\n let title, avatar;\n if (view == 'contacts') {\n title = this.props.title;\n avatar = this.props.avatar ? this.props.avatar : true;\n } else {\n title = formatMessage(messages[view]);\n avatar = false;\n }\n\n let onCancel;\n if (['login', 'contacts'].indexOf(view) == -1) {\n onCancel = this.props.onCancel;\n }\n\n return (\n \n
\n\n
\n\n
\n\n {view === 'login' ?\n
:\n\n view === 'register' ?\n
:\n\n view === 'settings' ?\n
:\n\n view === 'edit' ?\n
:\n\n (view === 'contacts' || view == 'archive') ?\n
:\n\n view === 'newtpk' ?\n
:\n\n view === 'cred' ?\n
:\n\n view === 'reset' ?\n
:\n null}\n
\n );\n }\n};\n\nexport default injectIntl(SidepanelView);\n","import { KNOWN_HOSTS, DEFAULT_HOST } from '../config.js';\n\n// Detect server address from the URL\nexport function detectServerAddress() {\n let host = DEFAULT_HOST;\n if (typeof window.location == 'object') {\n if (window.location.protocol == 'file:' || window.location.hostname == 'localhost') {\n host = KNOWN_HOSTS.local;\n } else if (window.location.hostname) {\n host = window.location.hostname + (window.location.port ? ':' + window.location.port : '');\n }\n }\n return host;\n}\n\n// Detect if the page is served over HTTPS.\nexport function isSecureConnection() {\n if (typeof window.location == 'object') {\n return window.location.protocol == 'https:';\n }\n return false;\n}\n\nexport function isLocalHost() {\n if (typeof window.location == 'object') {\n return window.location.hostname == 'localhost';\n }\n return false;\n}\n","// The top-level class to hold all functionality together.\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport * as firebase from 'firebase/app';\nimport 'firebase/messaging';\n\nimport Tinode from 'tinode-sdk';\n\nimport Alert from '../widgets/alert.jsx';\nimport ContextMenu from '../widgets/context-menu.jsx';\n\nimport InfoView from './info-view.jsx';\nimport MessagesView from './messages-view.jsx';\nimport SidepanelView from './sidepanel-view.jsx';\n\nimport { API_KEY, APP_NAME, DEFAULT_P2P_ACCESS_MODE, LOGGING_ENABLED, MEDIA_BREAKPOINT,\n READ_DELAY, RECEIVED_DELAY } from '../config.js';\nimport { base64ReEncode, makeImageUrl } from '../lib/blob-helpers.js';\nimport { detectServerAddress, isLocalHost, isSecureConnection } from '../lib/host-name.js';\nimport LocalStorageUtil from '../lib/local-storage.js';\nimport HashNavigation from '../lib/navigation.js';\nimport { secondsToTime } from '../lib/strformat.js'\nimport { updateFavicon } from '../lib/utils.js';\n\n// Sound to play on message received.\nconst POP_SOUND = new Audio('audio/msg.mp3');\n\nconst messages = defineMessages({\n update_available: {\n id: 'update_available',\n defaultMessage: 'Update available. Reload .',\n description: 'Message shown when an app update is available.'\n },\n reconnect_countdown: {\n id: 'reconnect_countdown',\n defaultMessage: 'Disconnected. Reconnecting in {seconds}…',\n description: 'Message shown when an app update is available.'\n },\n reconnect_now: {\n id: 'reconnect_now',\n defaultMessage: 'Try now',\n description: 'Prompt for reconnecting now'\n }\n});\n\nclass TinodeWeb extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = this.getBlankState();\n\n this.handleResize = this.handleResize.bind(this);\n this.handleHashRoute = this.handleHashRoute.bind(this);\n this.handleOnline = this.handleOnline.bind(this);\n this.checkForAppUpdate = this.checkForAppUpdate.bind(this);\n this.handleAppVisibility = this.handleAppVisibility.bind(this);\n this.handleReadTimer = this.handleReadTimer.bind(this);\n this.handleVisibilityEvent = this.handleVisibilityEvent.bind(this);\n this.handleError = this.handleError.bind(this);\n this.handleLoginRequest = this.handleLoginRequest.bind(this);\n this.handleConnected = this.handleConnected.bind(this);\n this.handleAutoreconnectIteration = this.handleAutoreconnectIteration.bind(this);\n this.doLogin = this.doLogin.bind(this);\n this.handleCredentialsRequest = this.handleCredentialsRequest.bind(this);\n this.handleLoginSuccessful = this.handleLoginSuccessful.bind(this);\n this.handleDisconnect = this.handleDisconnect.bind(this);\n this.tnMeMetaDesc = this.tnMeMetaDesc.bind(this);\n this.tnMeContactUpdate = this.tnMeContactUpdate.bind(this);\n this.tnMeSubsUpdated = this.tnMeSubsUpdated.bind(this);\n this.resetContactList = this.resetContactList.bind(this);\n this.tnData = this.tnData.bind(this);\n this.tnInitFind = this.tnInitFind.bind(this);\n this.tnFndSubsUpdated = this.tnFndSubsUpdated.bind(this);\n this.handleSearchContacts = this.handleSearchContacts.bind(this);\n this.handleTopicSelected = this.handleTopicSelected.bind(this);\n this.handleHideMessagesView = this.handleHideMessagesView.bind(this);\n this.handleSendMessage = this.handleSendMessage.bind(this);\n this.handleNewChatInvitation = this.handleNewChatInvitation.bind(this);\n this.handleNewAccount = this.handleNewAccount.bind(this);\n this.handleNewAccountRequest = this.handleNewAccountRequest.bind(this);\n this.handleUpdateAccountRequest = this.handleUpdateAccountRequest.bind(this);\n this.handleUpdateAccountTagsRequest = this.handleUpdateAccountTagsRequest.bind(this);\n this.handleSettings = this.handleSettings.bind(this);\n this.handleGlobalSettings = this.handleGlobalSettings.bind(this);\n this.handleShowArchive = this.handleShowArchive.bind(this);\n this.handleToggleMessageSounds = this.handleToggleMessageSounds.bind(this);\n this.handleCredAdd = this.handleCredAdd.bind(this);\n this.handleCredDelete = this.handleCredDelete.bind(this);\n this.handleCredConfirm = this.handleCredConfirm.bind(this);\n this.initDesktopAlerts = this.initDesktopAlerts.bind(this);\n this.togglePushToken = this.togglePushToken.bind(this);\n this.requestPushToken = this.requestPushToken.bind(this);\n this.handleSidepanelCancel = this.handleSidepanelCancel.bind(this);\n this.handleNewTopic = this.handleNewTopic.bind(this);\n this.handleNewTopicRequest = this.handleNewTopicRequest.bind(this);\n this.handleNewTopicCreated = this.handleNewTopicCreated.bind(this);\n this.handleTopicUpdateRequest = this.handleTopicUpdateRequest.bind(this);\n this.handleChangePermissions = this.handleChangePermissions.bind(this);\n this.handleTagsUpdated = this.handleTagsUpdated.bind(this);\n this.handleLogout = this.handleLogout.bind(this);\n this.handleDeleteMessagesRequest = this.handleDeleteMessagesRequest.bind(this);\n this.handleLeaveUnsubRequest = this.handleLeaveUnsubRequest.bind(this);\n this.handleBlockTopicRequest = this.handleBlockTopicRequest.bind(this);\n this.handleReportTopic = this.handleReportTopic.bind(this);\n this.handleShowContextMenu = this.handleShowContextMenu.bind(this);\n this.defaultTopicContextMenu = this.defaultTopicContextMenu.bind(this);\n this.handleHideContextMenu = this.handleHideContextMenu.bind(this);\n this.handleShowAlert = this.handleShowAlert.bind(this);\n this.handleShowInfoView = this.handleShowInfoView.bind(this);\n this.handleHideInfoView = this.handleHideInfoView.bind(this);\n this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);\n this.handleValidateCredentialsRequest = this.handleValidateCredentialsRequest.bind(this);\n this.handlePasswordResetRequest = this.handlePasswordResetRequest.bind(this);\n this.handleResetPassword = this.handleResetPassword.bind(this);\n this.handleContextMenuAction = this.handleContextMenuAction.bind(this);\n }\n\n getBlankState() {\n const settings = LocalStorageUtil.getObject('settings') || {};\n\n return {\n connected: false,\n // Connected and subscribed to 'me'\n ready: false,\n transport: settings.transport || null,\n serverAddress: settings.serverAddress || detectServerAddress(),\n serverVersion: \"no connection\",\n // \"On\" is the default, so saving the \"off\" state.\n messageSounds: !settings.messageSoundsOff,\n desktopAlerts: settings.desktopAlerts,\n desktopAlertsEnabled: (isSecureConnection() || isLocalHost()) &&\n (typeof firebase != 'undefined') && (typeof navigator != 'undefined') &&\n (typeof FIREBASE_INIT != 'undefined'),\n firebaseToken: LocalStorageUtil.getObject('firebase-token'),\n\n errorText: '',\n errorLevel: null,\n errorAction: undefined,\n errorActionText: null,\n\n sidePanelSelected: 'login',\n sidePanelTitle: null,\n sidePanelAvatar: null,\n loadSpinnerVisible: false,\n\n login: '',\n password: '',\n myUserId: null,\n liveConnection: navigator.onLine,\n topicSelected: '',\n topicSelectedOnline: false,\n topicSelectedAcs: null,\n newTopicParams: null,\n loginDisabled: false,\n displayMobile: (window.innerWidth <= MEDIA_BREAKPOINT),\n showInfoPanel: false,\n mobilePanel: 'sidepanel',\n\n contextMenuVisible: false,\n contextMenuBounds: null,\n contextMenuClickAt: null,\n contextMenuParams: null,\n contextMenuItems: [],\n\n // Modal alert dialog.\n alertVisible: false,\n alertParams: {},\n\n // Chats as shown in the ContactsView\n chatList: [],\n // Contacts returned by a search query.\n searchResults: [],\n // Merged results of a search query and p2p chats.\n searchableContacts: [],\n credMethod: undefined,\n credCode: undefined\n };\n }\n\n componentDidMount() {\n window.addEventListener('resize', this.handleResize);\n window.addEventListener('online', (e) => { this.handleOnline(true); });\n window.addEventListener('offline', (e) => { this.handleOnline(false); });\n window.addEventListener('hashchange', this.handleHashRoute);\n // Window/tab visible or invisible for pausing timers.\n document.addEventListener('visibilitychange', this.handleVisibilityEvent);\n\n this.setState({\n viewportWidth: document.documentElement.clientWidth,\n viewportHeight: document.documentElement.clientHeight\n });\n\n this.tinode = TinodeWeb.tnSetup(this.state.serverAddress, this.state.transport);\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n this.tinode.onAutoreconnectIteration = this.handleAutoreconnectIteration;\n\n // Initialize desktop alerts.\n if (this.state.desktopAlertsEnabled) {\n try {\n this.fbPush = firebase.initializeApp(FIREBASE_INIT, APP_NAME).messaging();\n this.fbPush.usePublicVapidKey(FIREBASE_INIT.messagingVapidKey);\n navigator.serviceWorker.register('/service-worker.js').then((reg) => {\n this.checkForAppUpdate(reg);\n this.fbPush.useServiceWorker(reg);\n this.initDesktopAlerts();\n if (this.state.desktopAlerts) {\n if (!this.state.firebaseToken) {\n this.togglePushToken(true);\n } else {\n this.tinode.setDeviceToken(this.state.firebaseToken, true);\n }\n }\n }).catch((err) => {\n // registration failed :(\n console.log(\"Failed to register service worker:\", err);\n });\n } catch (err) {\n this.handleError(\"Failed to initialize push notifications\", 'err');\n console.log(\"Failed to initialize push notifications\", err);\n this.setState({desktopAlertsEnabled: false});\n }\n }\n\n const token = LocalStorageUtil.getObject('keep-logged-in') ?\n LocalStorageUtil.getObject('auth-token') : undefined;\n\n const parsedNav = HashNavigation.parseUrlHash(window.location.hash);\n if (token) {\n // When reading from storage, date is returned as string.\n token.expires = new Date(token.expires);\n this.tinode.setAuthToken(token);\n this.tinode.connect().catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n delete parsedNav.params.info;\n delete parsedNav.params.tab;\n parsedNav.path[0] = '';\n HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsedNav.path, parsedNav.params));\n } else if (!parsedNav.params.token) {\n HashNavigation.navigateTo('');\n }\n\n this.readTimer = null;\n this.readTimerCallback = null;\n\n this.handleHashRoute();\n }\n\n componentWillUnmount() {\n window.removeEventListener('resize', this.handleResize);\n window.removeEventListener('hashchange', this.handleHashRoute);\n document.removeEventListener('visibilitychange', this.handleVisibilityEvent);\n }\n\n // Setup transport (usually websocket) and server address. This will terminate connection with the server.\n static tnSetup(serverAddress, transport) {\n let tinode = new Tinode(APP_NAME, serverAddress, API_KEY, transport, isSecureConnection());\n tinode.enableLogging(LOGGING_ENABLED, true);\n return tinode;\n }\n\n handleResize() {\n var mobile = document.documentElement.clientWidth <= MEDIA_BREAKPOINT;\n this.setState({\n viewportWidth: document.documentElement.clientWidth,\n viewportHeight: document.documentElement.clientHeight\n });\n if (this.state.displayMobile != mobile) {\n this.setState({displayMobile: mobile});\n }\n }\n\n // Check if a newer version of TinodeWeb app is available at the server.\n checkForAppUpdate(reg) {\n const {formatHTMLMessage} = this.props.intl;\n reg.onupdatefound = () => {\n const installingWorker = reg.installing;\n installingWorker.onstatechange = () => {\n if (installingWorker.state == 'installed' && navigator.serviceWorker.controller) {\n this.handleError(formatHTMLMessage(messages.update_available), 'info');\n }\n }\n }\n }\n\n // Handle for hashchange event: display appropriate panels.\n handleHashRoute() {\n var hash = HashNavigation.parseUrlHash(window.location.hash);\n if (hash.path && hash.path.length > 0) {\n // Left-side panel selector.\n if (['register','settings','edit','cred','reset','newtpk','archive','contacts',''].includes(hash.path[0])) {\n this.setState({sidePanelSelected: hash.path[0]});\n } else {\n console.log(\"Unknown sidepanel view\", hash.path[0]);\n }\n\n // Topic for MessagesView selector.\n if (hash.path.length > 1 && hash.path[1] != this.state.topicSelected) {\n this.setState({\n topicSelected: Tinode.topicType(hash.path[1]) ? hash.path[1] : null\n });\n }\n } else {\n // Empty hashpath\n this.setState({sidePanelSelected: ''});\n }\n\n // Save validation credentials, if available.\n if (hash.params.method) {\n this.setState({ credMethod: hash.params.method });\n }\n if (hash.params.code) {\n this.setState({ credCode: hash.params.code });\n }\n\n // Additional parameters of panels.\n this.setState({\n showInfoPanel: hash.params.info,\n newTopicTabSelected: hash.params.tab\n });\n }\n\n handleOnline(online) {\n var newState = {liveConnection: online};\n if (online) {\n this.handleError('', null);\n } else {\n this.handleError(\"No connection\", 'warn');\n }\n this.setState({liveConnection: online});\n }\n\n // Handling read notifications here to be able to pause\n // them then the window/tab is not visible.\n handleAppVisibility(visible, callback) {\n clearTimeout(this.readTimer);\n this.readTimerCallback = callback;\n if (visible && callback) {\n this.readTimer = setTimeout(callback, READ_DELAY);\n } else {\n this.readTimer = null;\n }\n }\n\n handleReadTimer(callback) {\n this.handleAppVisibility(!document.hidden, callback);\n }\n\n handleVisibilityEvent() {\n this.handleAppVisibility(!document.hidden, this.readTimerCallback);\n }\n\n handleError(err, level, action, actionText) {\n this.setState({errorText: err, errorLevel: level, errorAction: action, errorActionText: actionText});\n }\n\n // User clicked Login button in the side panel.\n handleLoginRequest(login, password) {\n this.setState({\n loginDisabled: true,\n login: login,\n password: password,\n loadSpinnerVisible: true\n });\n this.handleError('', null);\n\n if (this.tinode.isConnected()) {\n this.doLogin(login, password, {meth: this.state.credMethod, resp: this.state.credCode});\n } else {\n this.tinode.connect().catch((err) => {\n // Socket error\n this.setState({loginDisabled: false});\n this.handleError(err.message, 'err');\n });\n }\n }\n\n // Connection succeeded.\n handleConnected() {\n var params = this.tinode.getServerInfo();\n this.setState({\n serverVersion: params.ver + ' ' + (params.build ? params.build : 'none') + '; '\n });\n this.doLogin(this.state.login, this.state.password, {meth: this.state.credMethod, resp: this.state.credCode});\n }\n\n // Called for each auto-reconnect iteration.\n handleAutoreconnectIteration(sec, prom) {\n clearInterval(this.reconnectCountdown);\n\n if (sec < 0) {\n // Clear error\n this.handleError();\n return;\n }\n\n if (prom) {\n // Reconnecting now\n prom.then(() => {\n // Reconnected: clear error\n this.handleError();\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n return;\n }\n\n const {formatHTMLMessage} = this.props.intl;\n let count = sec / 1000;\n count = count | count;\n this.reconnectCountdown = setInterval(() => {\n const timeLeft = (count > 99) ? secondsToTime(count) : count;\n this.handleError(\n formatHTMLMessage(messages.reconnect_countdown, {seconds: timeLeft}),\n 'warn',\n () => {\n clearInterval(this.reconnectCountdown);\n this.tinode.reconnect();\n },\n formatHTMLMessage(messages.reconnect_now)\n );\n count -= 1;\n }, 1000);\n }\n\n // Connection lost\n handleDisconnect(err) {\n this.setState({\n connected: false,\n ready: false,\n topicSelectedOnline: false,\n errorText: err && err.message ? err.message : \"Disconnected\",\n errorLevel: err && err.message ? 'err' : 'warn',\n loginDisabled: false,\n contextMenuVisible: false,\n serverVersion: \"no connection\"\n });\n }\n\n doLogin(login, password, cred) {\n if (this.tinode.isAuthenticated()) {\n // Already logged in. Go to default screen.\n HashNavigation.navigateTo('');\n return;\n }\n // Sanitize and package credentail.\n cred = Tinode.credential(cred);\n // Try to login with login/password. If they are not available, try token. If no token, ask for login/password.\n let promise = null;\n let token = this.tinode.getAuthToken();\n if (login && password) {\n this.setState({password: null});\n promise = this.tinode.loginBasic(login, password, cred);\n } else if (token) {\n promise = this.tinode.loginToken(token.token, cred);\n }\n\n if (promise) {\n promise.then((ctrl) => {\n if (ctrl.code >= 300 && ctrl.text === 'validate credentials') {\n this.setState({loadSpinnerVisible: false});\n if (cred) {\n this.handleError(\"Code does not match\", 'warn');\n }\n this.handleCredentialsRequest(ctrl.params);\n } else {\n this.handleLoginSuccessful();\n }\n }).catch((err) => {\n // Login failed, report error.\n this.setState({\n loginDisabled: false,\n credMethod: undefined,\n credCode: undefined,\n loadSpinnerVisible: false\n });\n this.handleError(err.message, 'err');\n localStorage.removeItem('auth-token');\n HashNavigation.navigateTo('');\n });\n } else {\n // No login credentials provided.\n // Make sure we are on the login page.\n HashNavigation.navigateTo('');\n this.setState({loginDisabled: false});\n }\n }\n\n handleCredentialsRequest(params) {\n var parsed = HashNavigation.parseUrlHash(window.location.hash);\n parsed.path[0] = 'cred';\n parsed.params['method'] = params.cred[0];\n HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));\n }\n\n handleLoginSuccessful() {\n this.handleError('', null);\n\n // Refresh authentication token.\n if (LocalStorageUtil.getObject('keep-logged-in')) {\n LocalStorageUtil.setObject('auth-token', this.tinode.getAuthToken());\n }\n // Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.\n var me = this.tinode.getMeTopic();\n me.onMetaDesc = this.tnMeMetaDesc;\n me.onContactUpdate = this.tnMeContactUpdate;\n me.onSubsUpdated = this.tnMeSubsUpdated;\n this.setState({\n connected: true,\n credMethod: undefined,\n credCode: undefined,\n myUserId: this.tinode.getCurrentUserID()\n });\n // Subscribe, fetch topic desc, the list of subscriptions. Messages are not fetched.\n me.subscribe(\n me.startMetaQuery().\n withLaterSub().\n withDesc().\n withTags().\n withCred().\n build()\n ).catch((err) => {\n this.tinode.disconnect();\n localStorage.removeItem('auth-token');\n this.handleError(err.message, 'err');\n HashNavigation.navigateTo('');\n }).finally(() => {\n this.setState({loadSpinnerVisible: false});\n });\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'contacts'));\n }\n\n tnMeMetaDesc(desc) {\n if (desc && desc.public) {\n this.setState({\n sidePanelTitle: desc.public.fn,\n sidePanelAvatar: makeImageUrl(desc.public.photo)\n });\n }\n }\n\n // Reactions to updates to the contact list.\n tnMeContactUpdate(what, cont) {\n if (what == 'on' || what == 'off') {\n this.resetContactList();\n if (this.state.topicSelected == cont.topic) {\n this.setState({topicSelectedOnline: (what == 'on')});\n }\n } else if (what == 'read') {\n this.resetContactList();\n } else if (what == 'msg') {\n // Check if the topic is archived. If so, don't play a sound.\n const topic = this.tinode.getTopic(cont.topic);\n const archived = topic && topic.isArchived();\n\n // New message received. Maybe the message is from the current user, then unread is 0.\n if (cont.unread > 0 && this.state.messageSounds && !archived) {\n // Skip update if the topic is currently open, otherwise the badge will annoyingly flash.\n if (document.hidden || this.state.topicSelected != cont.topic) {\n POP_SOUND.play();\n }\n }\n // Reorder contact list to use possibly updated 'touched'.\n this.resetContactList();\n } else if (what == 'recv') {\n // Explicitly ignoring \"recv\" -- it causes no visible updates to contact list.\n } else if (what == 'gone' || what == 'unsub') {\n // Topic deleted or user unsubscribed. Remove topic from view.\n // If the currently selected topic is gone, clear the selection.\n if (this.state.topicSelected == cont.topic) {\n this.handleTopicSelected(null);\n }\n // Redraw without the deleted topic.\n this.resetContactList();\n } else if (what == 'acs') {\n // Permissions changed. If it's for the currently selected topic,\n // update the views.\n if (this.state.topicSelected == cont.topic) {\n this.setState({topicSelectedAcs: cont.acs});\n }\n } else if (what == 'del') {\n // TODO: messages deleted (hard or soft) -- update pill counter.\n } else if (what == 'upd') {\n // upd - handled by the SDK. Explicitly ignoring here.\n } else {\n // TODO(gene): handle other types of notifications:\n // * ua -- user agent changes (maybe display a pictogram for mobile/desktop).\n console.log(\"Unsupported (yet) presence update:\" + what + \" in: \" + cont.topic);\n }\n }\n\n tnMeSubsUpdated(unused) {\n this.resetContactList();\n }\n\n // Merge search results and contact list to create a single flat\n // list of kown contacts for GroupManager to use.\n static prepareSearchableContacts(chatList, foundContacts) {\n let merged = {};\n\n // For chatList topics merge only p2p topics and convert them to the\n // same format as foundContacts.\n for (const c of chatList) {\n if (Tinode.topicType(c.topic) == 'p2p') {\n merged[c.topic] = {\n user: c.topic,\n updated: c.updated,\n public: c.public,\n private: c.private,\n acs: c.acs\n };\n }\n }\n\n // Add all foundCountacts if they have not been added already.\n for (const c of foundContacts) {\n if (!merged[c.user]) {\n merged[c.user] = c;\n }\n }\n\n return Object.values(merged);\n }\n\n resetContactList() {\n const newState = {\n chatList: []\n };\n\n if (!this.state.ready) {\n newState.ready = true;\n }\n\n this.tinode.getMeTopic().contacts((c) => {\n newState.chatList.push(c);\n if (this.state.topicSelected == c.topic) {\n newState.topicSelectedOnline = c.online;\n newState.topicSelectedAcs = c.acs;\n }\n });\n // Merge search results and chat list.\n newState.searchableContacts = TinodeWeb.prepareSearchableContacts(newState.chatList, this.state.searchResults);\n this.setState(newState);\n }\n\n // Sending \"received\" notifications\n tnData(data) {\n const topic = this.tinode.getTopic(data.topic);\n if (topic.msgStatus(data) >= Tinode.MESSAGE_STATUS_SENT && data.from != this.state.myUserId) {\n clearTimeout(this.receivedTimer);\n this.receivedTimer = setTimeout(() => {\n this.receivedTimer = undefined;\n topic.noteRecv(data.seq);\n }, RECEIVED_DELAY);\n }\n }\n\n /* Fnd topic: find contacts by tokens */\n tnInitFind() {\n const fnd = this.tinode.getFndTopic();\n fnd.onSubsUpdated = this.tnFndSubsUpdated;\n if (fnd.isSubscribed()) {\n this.tnFndSubsUpdated();\n } else {\n fnd.subscribe(fnd.startMetaQuery().withSub().build()).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n tnFndSubsUpdated() {\n const foundContacts = [];\n // Don't attempt to create P2P topics which already exist. Server will reject the duplicates.\n this.tinode.getFndTopic().contacts((s) => {\n foundContacts.push(s);\n });\n this.setState({\n searchResults: foundContacts,\n searchableContacts: TinodeWeb.prepareSearchableContacts(this.state.chatList, foundContacts)\n });\n }\n\n /** Called when the user enters a contact into the contact search field in the NewAccount panel\n @param query {Array} is an array of contacts to search for\n */\n handleSearchContacts(query) {\n const fnd = this.tinode.getFndTopic();\n fnd.setMeta({desc: {public: query}}).then((ctrl) => {\n return fnd.getMeta(fnd.startMetaQuery().withSub().build());\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n // User clicked on a contact in the side panel or deleted a contact.\n handleTopicSelected(topicName, unused_index, online, acs) {\n // Clear newTopicParams after use.\n if (this.state.newTopicParams && this.state.newTopicParams._topicName != topicName) {\n this.setState({\n newTopicParams: null\n });\n }\n\n if (topicName) {\n this.setState({\n errorText: '',\n errorLevel: null,\n mobilePanel: 'topic-view',\n showInfoPanel: false\n });\n // Different contact selected.\n if (this.state.topicSelected != topicName) {\n this.setState({\n topicSelectedOnline: online,\n topicSelectedAcs: acs\n });\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', topicName));\n }\n } else {\n // Currently selected contact deleted\n this.setState({\n errorText: '',\n errorLevel: null,\n mobilePanel: 'sidepanel',\n topicSelectedOnline: false,\n topicSelectedAcs: null,\n showInfoPanel: false\n });\n\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', null));\n }\n }\n\n // In mobile view user requested to show sidepanel\n handleHideMessagesView() {\n this.setState({\n mobilePanel: 'sidepanel'\n });\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, null));\n }\n\n // User is sending a message, either plain text or a drafty object, possibly\n // with attachments.\n handleSendMessage(msg, promise, uploader) {\n const topic = this.tinode.getTopic(this.state.topicSelected);\n\n msg = topic.createMessage(msg, false);\n // The uploader is used to show progress.\n msg._uploader = uploader;\n\n if (!topic.isSubscribed()) {\n if (!promise) {\n promise = Promise.resolve();\n }\n promise = promise.then(() => { return topic.subscribe(); });\n }\n\n if (promise) {\n promise = promise.catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n topic.publishDraft(msg, promise)\n .then((ctrl) => {\n if (topic.isArchived()) {\n return topic.archive(false);\n }\n })\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleNewChatInvitation(topicName, action) {\n const topic = this.tinode.getTopic(topicName);\n let response = null;\n switch (action) {\n case 'accept':\n // Accept given permissions.\n const mode = topic.getAccessMode().getGiven();\n response = topic.setMeta({sub: {mode: mode}});\n if (topic.getType() == 'p2p') {\n // For P2P topics change 'given' permission of the peer too.\n // In p2p topics the other user has the same name as the topic.\n response = response.then((ctrl) => {\n topic.setMeta({sub: {user: topicName, mode: mode}});\n });\n }\n break;\n case 'delete':\n // Ignore invitation by deleting it.\n response = topic.delTopic();\n break;\n case 'block':\n // Ban the topic making futher invites impossible.\n // Just self-ban.\n const am = topic.getAccessMode().updateWant('-JP').getWant();\n response = topic.setMeta({sub: {mode: am}}).then((ctrl) => {\n return this.handleTopicSelected(null);\n });\n break;\n default:\n console.log(\"Unknown invitation action\", '\"' + action + '\"\"');\n }\n\n if (response != null) {\n response.catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n // User chose a Sign Up menu item.\n handleNewAccount() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'register'));\n }\n\n // Actual registration of a new account.\n handleNewAccountRequest(login_, password_, public_, cred_, tags_) {\n this.tinode.connect(this.state.serverAddress)\n .then(() => {\n return this.tinode.createAccountBasic(login_, password_,\n {public: public_, tags: tags_, cred: Tinode.credential(cred_)});\n }).then((ctrl) => {\n if (ctrl.code >= 300 && ctrl.text == 'validate credentials') {\n this.handleCredentialsRequest(ctrl.params);\n } else {\n this.handleLoginSuccessful(this);\n }\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleUpdateAccountRequest(password, pub, defacs) {\n if (pub || defacs) {\n const params = {};\n if (pub) {\n params.public = pub;\n }\n if (defacs) {\n params.defacs = defacs;\n }\n this.tinode.getMeTopic().setMeta({desc: params}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n if (password) {\n this.tinode.updateAccountBasic(null, this.tinode.getCurrentLogin(), password).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleUpdateAccountTagsRequest(tags) {\n this.tinode.getMeTopic().setMeta({tags: tags})\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n // User chose Settings menu item.\n handleSettings() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash,\n this.state.myUserId ? 'edit' : 'settings'));\n }\n\n // User updated global parameters.\n handleGlobalSettings(settings) {\n let serverAddress = settings.serverAddress || this.state.serverAddress;\n let transport = settings.transport || this.state.transport;\n if (this.tinode) {\n this.tinode.onDisconnect = undefined;\n this.tinode.disconnect();\n }\n this.tinode = TinodeWeb.tnSetup(serverAddress, transport);\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n\n this.setState({\n serverAddress: serverAddress,\n transport: transport\n });\n LocalStorageUtil.setObject('settings', {\n serverAddress: serverAddress,\n transport: transport\n });\n\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, ''));\n }\n\n // User chose 'Archived chats'.\n handleShowArchive() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash,\n this.state.myUserId ? 'archive' : ''));\n }\n\n // Initialize desktop alerts = push notifications.\n initDesktopAlerts() {\n // Google could not be bothered to mention that\n // onTokenRefresh is never called.\n this.fbPush.onTokenRefresh(() => {\n this.requestPushToken();\n });\n\n this.fbPush.onMessage((payload) => {\n // No need to do anything about it.\n // All the magic happends in the service worker.\n });\n }\n\n togglePushToken(enabled) {\n if (enabled) {\n if (!this.state.firebaseToken) {\n this.fbPush.requestPermission().then(() => {\n this.requestPushToken();\n }).catch((err) => {\n this.handleError(err.message, 'err');\n this.setState({desktopAlerts: false, firebaseToken: null});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n console.log(\"Failed to get permission to notify.\", err);\n });\n } else {\n this.setState({desktopAlerts: true});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: true});\n }\n } else if (this.state.firebaseToken) {\n this.fbPush.deleteToken(this.state.firebaseToken).catch((err) => {\n console.log(\"Unable to delete token.\", err);\n }).finally(() => {\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n localStorage.removeItem('firebase-token');\n this.setState({desktopAlerts: false, firebaseToken: null});\n });\n } else {\n this.setState({desktopAlerts: false, firebaseToken: null});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n }\n }\n\n requestPushToken() {\n this.fbPush.getToken().then((refreshedToken) => {\n if (refreshedToken != this.state.firebaseToken) {\n this.tinode.setDeviceToken(refreshedToken, true);\n LocalStorageUtil.setObject('firebase-token', refreshedToken);\n }\n this.setState({firebaseToken: refreshedToken, desktopAlerts: true});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: true});\n }).catch((err) => {\n this.handleError(err.message, 'err');\n console.log(\"Failed to retrieve firebase token\", err);\n });\n }\n\n handleToggleMessageSounds(enabled) {\n this.setState({messageSounds: enabled});\n LocalStorageUtil.updateObject('settings', {\n messageSoundsOff: !enabled\n });\n }\n\n handleCredAdd(method, value) {\n const me = this.tinode.getMeTopic();\n me.setMeta({cred: {meth: method, val: value}}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleCredDelete(method, value) {\n const me = this.tinode.getMeTopic();\n me.delCredential(method, value).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleCredConfirm(method, response) {\n this.handleCredentialsRequest({cred: [method]});\n }\n\n // User clicked Cancel button in Setting or Sign Up panel.\n handleSidepanelCancel() {\n const parsed = HashNavigation.parseUrlHash(window.location.hash);\n parsed.path[0] = this.state.myUserId ? 'contacts' : '';\n if (parsed.params) {\n delete parsed.params.code;\n delete parsed.params.method;\n delete parsed.params.tab;\n }\n HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));\n this.setState({errorText: '', errorLevel: null});\n }\n\n // User clicked a (+) menu item.\n handleNewTopic() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'newtpk'));\n }\n\n // Request to start a new topic. New P2P topic requires peer's name.\n handleNewTopicRequest(peerName, pub, priv, tags) {\n\n const topicName = peerName || this.tinode.newGroupTopicName();\n const params = {\n _topicName: topicName,\n };\n if (peerName) {\n // Because we are initialing the subscription, set 'want' to all permissions.\n params.sub = {mode: DEFAULT_P2P_ACCESS_MODE};\n // Give the other user all permissions too.\n params.desc = {defacs: {auth: DEFAULT_P2P_ACCESS_MODE}};\n } else {\n params.desc = {public: pub, private: {comment: priv}};\n params.tags = tags;\n }\n this.setState({newTopicParams: params}, () => {this.handleTopicSelected(topicName)});\n }\n\n // New topic was creted, here is the new topic name.\n handleNewTopicCreated(oldName, newName) {\n if (this.state.topicSelected == oldName && oldName != newName) {\n // If the current URl contains the old topic name, replace it with new.\n // Update the name of the selected topic first so the navigator doen't clear\n // the state.\n this.setState({topicSelected: newName}, () => {\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', newName));\n });\n }\n }\n\n handleTopicUpdateRequest(topicName, pub, priv, permissions) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n const params = {};\n if (pub) {\n params.public = pub;\n }\n if (priv) {\n params.private = (priv === Tinode.DEL_CHAR) ?\n Tinode.DEL_CHAR : {comment: priv};\n }\n if (permissions) {\n params.defacs = permissions;\n }\n topic.setMeta({desc: params}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleChangePermissions(topicName, mode, uid) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n const am = topic.getAccessMode();\n if (uid) {\n am.updateGiven(mode);\n mode = am.getGiven();\n } else {\n am.updateWant(mode);\n mode = am.getWant();\n }\n topic.setMeta({sub: {user: uid, mode: mode}}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleTagsUpdated(topicName, tags) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n topic.setMeta({tags: tags}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleLogout() {\n updateFavicon(0);\n\n // Remove stored data.\n localStorage.removeItem('auth-token');\n localStorage.removeItem('firebase-token');\n localStorage.removeItem('settings');\n if (this.state.firebaseToken) {\n this.fbPush.deleteToken(this.state.firebaseToken)\n }\n\n if (this.tinode) {\n this.tinode.onDisconnect = undefined;\n this.tinode.disconnect();\n }\n this.setState(this.getBlankState());\n this.tinode = TinodeWeb.tnSetup(this.state.serverAddress, this.state.transport);\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n HashNavigation.navigateTo('');\n }\n\n handleDeleteMessagesRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n // Request hard-delete all messages.\n topic.delMessagesAll(true).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleLeaveUnsubRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n topic.leave(true).then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleBlockTopicRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n topic.updateMode(null, '-JP').then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleReportTopic(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n // Publish spam report.\n this.tinode.publish(Tinode.TOPIC_SYS, Tinode.Drafty.attachJSON(null, {\n 'action': 'report',\n 'target': topicName\n }));\n\n // Remove J and P permissions.\n topic.updateMode(null, '-JP').then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleShowContextMenu(params, menuItems) {\n this.setState({\n contextMenuVisible: true,\n contextMenuClickAt: {x: params.x, y: params.y},\n contextMenuParams: params,\n contextMenuItems: menuItems || this.defaultTopicContextMenu(params.topicName),\n contextMenuBounds: ReactDOM.findDOMNode(this).getBoundingClientRect()\n });\n }\n\n defaultTopicContextMenu(topicName) {\n const topic = this.tinode.getTopic(topicName);\n const {formatMessage} = this.props.intl;\n\n let muted = false, blocked = false, self_blocked = false, subscribed = false, deleter = false, archived = false;\n if (topic) {\n subscribed = topic.isSubscribed();\n archived = topic.isArchived();\n\n const acs = topic.getAccessMode();\n if (acs) {\n muted = acs.isMuted();\n blocked = !acs.isJoiner();\n self_blocked = !acs.isJoiner('want');\n deleter = acs.isDeleter();\n }\n }\n\n return [\n subscribed ? {title: formatMessage({id: 'menu_item_info'}), handler: this.handleShowInfoView} : null,\n subscribed ? 'messages_clear' : null,\n subscribed && deleter ? 'messages_clear_hard' : null,\n muted ? (blocked ? null : 'topic_unmute') : 'topic_mute',\n self_blocked ? 'topic_unblock' : 'topic_block',\n !archived ? 'topic_archive' : null,\n 'topic_delete'\n ];\n }\n\n handleHideContextMenu() {\n this.setState({\n contextMenuVisible: false,\n contextMenuClickAt: null,\n contextMenuParams: null,\n contextMenuBounds: null\n });\n }\n\n handleContextMenuAction(action, promise, params) {\n if (action == 'topic_archive') {\n if (promise && params.topicName && params.topicName == this.state.topicSelected) {\n promise.then(() => {\n this.handleTopicSelected(null);\n });\n }\n }\n }\n\n handleShowAlert(title, content, onConfirm, confirm, onReject, reject) {\n this.setState({\n alertVisible: true,\n alertParams: {\n title: title,\n content: content,\n onConfirm: onConfirm,\n confirm: confirm,\n onReject: onReject,\n reject: reject\n }\n });\n }\n\n handleShowInfoView() {\n HashNavigation.navigateTo(HashNavigation.addUrlParam(window.location.hash, 'info', true));\n this.setState({showInfoPanel: true});\n }\n\n handleHideInfoView() {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'info'));\n this.setState({showInfoPanel: false});\n }\n\n handleMemberUpdateRequest(topicName, added, removed) {\n if (!topicName) {\n return;\n }\n\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n if (added && added.length > 0) {\n added.map((uid) => {\n topic.invite(uid, null).catch((err) => {\n this.handleError(err.message, 'err');\n });\n });\n }\n\n if (removed && removed.length > 0) {\n removed.map((uid) => {\n topic.delSubscription(uid).catch((err) => {\n this.handleError(err.message, 'err');\n });\n });\n }\n }\n\n handleValidateCredentialsRequest(cred, code) {\n if (this.tinode.isAuthenticated()) {\n const me = this.tinode.getMeTopic();\n me.setMeta({cred: {meth: cred, resp: code}})\n .then(() => {\n HashNavigation.navigateTo('');\n })\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n } else {\n this.setState({credMethod: cred, credCode: code});\n this.doLogin(null, null, {meth: cred, resp: code});\n }\n }\n\n handlePasswordResetRequest(method, value) {\n // If already connected, connnect() will return a resolved promise.\n this.tinode.connect()\n .then(() => {\n return this.tinode.requestResetAuthSecret('basic', method, value);\n })\n .catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n }\n\n handleResetPassword(scheme, newPassword, token) {\n token = base64ReEncode(token);\n if (!token) {\n this.handleError(\"Invalid security token\", 'err');\n } else {\n this.tinode.connect()\n .then(() => {\n return this.tinode.updateAccountBasic(null, null, newPassword, {token: token});\n })\n .catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n }\n }\n\n render() {\n return (\n \n {this.state.contextMenuVisible ?\n
{\n if (topicName == this.state.topicSelected) {\n this.handleTopicSelected(null);\n }\n }}\n onError={this.handleError} />\n :\n null\n }\n { this.setState({alertVisible: false}); }) : null}\n reject={this.state.alertParams.reject}\n onConfirm={() => { this.setState({alertVisible: false}); this.state.alertParams.onConfirm(); }}\n confirm={this.state.alertParams.confirm}\n />\n \n\n \n\n {this.state.showInfoPanel ?\n \n :\n null\n }\n \n );\n }\n};\n\nexport default injectIntl(TinodeWeb);\n","// Put all packages together.\n// Used to generate umd/index.prod.js\n\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport { IntlProvider } from 'react-intl';\n\nimport allMessages from './messages.json';\nimport TinodeWeb from './views/tinode-web.jsx';\nimport HashNavigation from './lib/navigation.js';\n\n// Detect human language to use in the UI:\n// Check parameters from URL hash #?hl=ru, then browser, then use 'en' as a fallback.\nconst { params } = HashNavigation.parseUrlHash(window.location.hash);\nconst language = (params && params.hl) ||\n (navigator.languages && navigator.languages[0]) ||\n navigator.language ||\n navigator.userLanguage ||\n 'en';\n\n// Get the base language 'en' from a more specific 'en_GB' or 'en-US' as a partial fallback.\nconst baseLanguage = language.toLowerCase().split(/[-_]/)[0];\n\n// Try the full locale first, then the locale without the region code, fallback to 'en'.\nconst messages =\n allMessages[language] ||\n allMessages[baseLanguage] ||\n allMessages.en;\n\nReactDOM.render(\n \n \n ,\n document.getElementById('mountPoint')\n);\n"],"sourceRoot":""}
\ No newline at end of file