diff --git a/css/base.css b/css/base.css index 119f2331..400263f0 100644 --- a/css/base.css +++ b/css/base.css @@ -181,9 +181,7 @@ textarea, input { outline: none; color: #666; } -textarea { - resize: none; -} + input[type="text"], input[type="email"], input[type="password"], @@ -234,6 +232,11 @@ input[type="password"].with-visibility { margin-right:-2rem; padding-right:2rem; } + +textarea { + resize: none; + padding-top: 0.45rem; +} /* END of input and buttons */ /* BEGIN scrollbar styling. */ @@ -392,6 +395,10 @@ div.alert .content { inset 0 -0.25rem 0.25rem -0.25rem rgba(0,0,0,0.15); } +.hr.thin { + height: 0.015rem; +} + /* Element which can take focus but shows no outline */ .group-focus { outline: none; @@ -777,6 +784,10 @@ i.search { overflow: hidden; } +.flex-column.narrow { + height: auto; +} + .scrollable-panel { overflow-y: auto; margin-top: 0.25rem; @@ -1279,10 +1290,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 +1321,12 @@ 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 */ +} + +#image-preview-container .material-icons { + font-size: 6rem; } /* Talk bubbles */ diff --git a/css/base.min.css b/css/base.min.css index d3f7b496..38f36e90 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}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}textarea{resize:none;padding-top:.45rem}::-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)}.hr.thin{height:.015rem}.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}.flex-column.narrow{height:auto}.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)}#image-preview-container .material-icons{font-size:6rem}.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/i18n/en.json b/src/i18n/en.json index b4e584b9..b5f55528 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -146,6 +146,7 @@ "search_placeholder": "List like email:alice@example.com, tel:17025550003...", "messaging_disabled_prompt": "Messaging disabled", "new_message_prompt": "New message", + "image_caption_prompt": "Image caption", "file_attachment_too_large": "The file size {size} exceeds the {limit} limit.", "cannot_initiate_file_upload": "Cannot initiate file upload.", "tags_not_found": "No tags defined. Add some.", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 37088586..c22e813b 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -150,5 +150,6 @@ "topic_delete_warning": "Вы действительно хотите удалить этот чат?", "topic_block_warning": "Вы действительно хотите заблокировать этот чат?", "search_no_results": "Ничего не найдено", - "deleted_content": "удалено" + "deleted_content": "удалено", + "image_caption_prompt": "Подпись к фото" } diff --git a/src/i18n/zh.json b/src/i18n/zh.json index 6e8412dd..edfcfe17 100644 --- a/src/i18n/zh.json +++ b/src/i18n/zh.json @@ -150,5 +150,6 @@ "topic_delete_warning": ":!![MISSING]!!:Are you sure you want to delete this conversation?", "topic_block_warning": ":!![MISSING]!!:Are you sure you want to block this conversation?", "search_no_results": ":!![MISSING]!!:Search returned no results", - "deleted_content": "内容已删除" + "deleted_content": "内容已删除", + "image_caption_prompt": "图片标题" } diff --git a/src/messages.json b/src/messages.json index 46658966..19c36312 100644 --- a/src/messages.json +++ b/src/messages.json @@ -147,6 +147,7 @@ "search_placeholder": "List like email:alice@example.com, tel:17025550003...", "messaging_disabled_prompt": "Messaging disabled", "new_message_prompt": "New message", + "image_caption_prompt": "Image caption", "file_attachment_too_large": "The file size {size} exceeds the {limit} limit.", "cannot_initiate_file_upload": "Cannot initiate file upload.", "tags_not_found": "No tags defined. Add some.", @@ -305,7 +306,8 @@ "topic_delete_warning": "Вы действительно хотите удалить этот чат?", "topic_block_warning": "Вы действительно хотите заблокировать этот чат?", "search_no_results": "Ничего не найдено", - "deleted_content": "удалено" + "deleted_content": "удалено", + "image_caption_prompt": "Подпись к фото" }, "zh": { "archived_contacts": "已归档联系人 ({count})", @@ -450,6 +452,7 @@ "action_delete_messages": "删除所有帖子", "action_block_contact": "屏蔽联系人", "action_report_chat": "检举垃圾邮件", - "deleted_content": "内容已删除" + "deleted_content": "内容已删除", + "image_caption_prompt": "图片标题" } } diff --git a/src/views/messages-view.jsx b/src/views/messages-view.jsx index 23e2e7f3..c3f4c1a4 100644 --- a/src/views/messages-view.jsx +++ b/src/views/messages-view.jsx @@ -5,6 +5,7 @@ import Tinode from 'tinode-sdk'; const Drafty = Tinode.Drafty; import ChatMessage from '../widgets/chat-message.jsx'; +import DocPreview from '../widgets/doc-preview.jsx'; import ErrorPanel from '../widgets/error-panel.jsx'; import GroupSubs from '../widgets/group-subs.jsx'; import ImagePreview from '../widgets/image-preview.jsx'; @@ -14,9 +15,11 @@ 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 { shortDateFormat } from '../lib/strformat.js'; +import { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, + 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 { bytesToHumanSize, shortDateFormat } from '../lib/strformat.js'; const messages = defineMessages({ online_now: { @@ -61,6 +64,9 @@ class MessagesView extends React.Component { // this.propsChange = this.propsChange.bind(this); this.leave = this.leave.bind(this); + this.sendImageAttachment = this.sendImageAttachment.bind(this); + this.sendFileAttachment = this.sendFileAttachment.bind(this); + this.sendKeyPress = this.sendKeyPress.bind(this); this.handleScrollReference = this.handleScrollReference.bind(this); this.handleScrollEvent = this.handleScrollEvent.bind(this); this.handleDescChange = this.handleDescChange.bind(this); @@ -68,13 +74,15 @@ class MessagesView extends React.Component { this.handleNewMessage = this.handleNewMessage.bind(this); this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this); this.handleInfoReceipt = this.handleInfoReceipt.bind(this); - this.handleImagePreview = this.handleImagePreview.bind(this); - this.handleCloseImagePreview = this.handleCloseImagePreview.bind(this); + this.handleImagePostview = this.handleImagePostview.bind(this); + this.handleClosePreview = this.handleClosePreview.bind(this); this.handleFormResponse = this.handleFormResponse.bind(this); this.handleContextClick = this.handleContextClick.bind(this); this.handleShowContextMenuMessage = this.handleShowContextMenuMessage.bind(this); this.handleNewChatAcceptance = this.handleNewChatAcceptance.bind(this); this.handleEnablePeer = this.handleEnablePeer.bind(this); + this.handleAttachFile = this.handleAttachFile.bind(this); + this.handleAttachImage = this.handleAttachImage.bind(this); } componentDidMount() { @@ -169,7 +177,9 @@ class MessagesView extends React.Component { topic: null, title: '', avatar: null, + docPreview: null, imagePreview: null, + imagePostview: null, typingIndicator: false, scrollPosition: 0, fetchingMessages: false, @@ -179,7 +189,9 @@ class MessagesView extends React.Component { const topic = nextProps.tinode.getTopic(nextProps.topic); nextState = { topic: nextProps.topic, + docPreview: null, imagePreview: null, + imagePostview: null, typingIndicator: false, scrollPosition: 0, fetchingMessages: false @@ -432,12 +444,12 @@ class MessagesView extends React.Component { } } - handleImagePreview(content) { - this.setState({ imagePreview: content }); + handleImagePostview(content) { + this.setState({ imagePostview: content }); } - handleCloseImagePreview() { - this.setState({ imagePreview: null }); + handleClosePreview() { + this.setState({ imagePostview: null, imagePreview: null, docPreview: null }); } handleFormResponse(action, text, data) { @@ -492,170 +504,296 @@ class MessagesView extends React.Component { this.props.onChangePermissions(this.state.topic, DEFAULT_P2P_ACCESS_MODE, this.state.topic); } + sendKeyPress() { + const topic = this.props.tinode.getTopic(this.state.topic); + if (topic.isSubscribed()) { + topic.noteKeyPress(); + } + } + + sendFileAttachment(file) { + if (file.size > MAX_INBAND_ATTACHMENT_SIZE) { + // Too large to send inband - uploading out of band and sending as a link. + const uploader = this.props.tinode.getLargeFileHelper(); + if (!uploader) { + this.props.onError(formatMessage(messages.cannot_initiate_upload)); + return; + } + const uploadCompletionPromise = uploader.upload(file); + const msg = Drafty.attachFile(null, file.type, null, file.name, file.size, uploadCompletionPromise); + // Pass data and the uploader to the TinodeWeb. + this.props.sendMessage(msg, uploadCompletionPromise, uploader); + } else { + // Small enough to send inband. + fileToBase64(file, + (mime, bits, fname) => { + this.props.sendMessage(Drafty.attachFile(null, mime, bits, fname)); + }, + this.props.onError + ); + } + } + + handleAttachFile(file) { + if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) { + // Too large. + this.props.onError(formatMessage(messages.file_attachment_too_large, + {size: bytesToHumanSize(file.size), limit: bytesToHumanSize(MAX_EXTERN_ATTACHMENT_SIZE)}), 'err'); + } else { + this.setState({ docPreview: { + file: file, + filename: file.name, + size: file.size, + type: file.type + }}); + } + } + + sendImageAttachment(caption, mime, bits, width, height, fname) { + let msg = Drafty.insertImage(null, 0, mime, bits, width, height, fname); + if (caption) { + msg = Drafty.appendLineBreak(msg); + msg = Drafty.append(msg, Drafty.init(caption)); + } + this.props.sendMessage(msg); + } + + 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.setState({imagePreview: { + url: URL.createObjectURL(file), + bits: bits, + 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.setState({imagePreview: { + url: URL.createObjectURL(file), + bits: bits, + 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 ? - {e.preventDefault(); this.props.onHideMessagesView();}}> - arrow_back - - : - null} -
- - -
-
-
{ - this.state.title || - - }
-
{lastSeen}
-
- {groupTopic ? - : -
- } - -
- {this.props.displayMobile ? - - : null} - -
-
-
    - {messageNodes} -
-
- {!this.state.isReader ? -
- {this.state.readingBlocked ? -
- -
- : null } -
- : null } -
- {this.state.peerMessagingDisabled && !this.state.unconfirmed ? -
- block . -
: 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) { + // Preview image before sending. + component2 = ( + + ); + } else if (this.state.imagePostview) { + // Expand received image. + component2 = ( + + ); + } else if (this.state.docPreview) { + // Preview attachment before sending. + 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 ? + {e.preventDefault(); this.props.onHideMessagesView();}}> + arrow_back + + : + null} +
+ + +
+
+
{ + this.state.title || + + }
+
{lastSeen}
+
+ {groupTopic ? + : +
+ } + +
+ {this.props.displayMobile ? + + : null} + +
+
+
    + {messageNodes} +
+
+ {!this.state.isReader ? +
+ {this.state.readingBlocked ? +
+ +
+ : null } +
+ : null } +
+ {this.state.peerMessagingDisabled && !this.state.unconfirmed ? +
+ block . +
: 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/doc-preview.jsx b/src/widgets/doc-preview.jsx new file mode 100644 index 00000000..ac212190 --- /dev/null +++ b/src/widgets/doc-preview.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import SendMessage from '../widgets/send-message.jsx'; + +import { bytesToHumanSize } from '../lib/strformat.js'; + +// Get material icon name from mime type. +function iconFromMime(mime) { + // If more icons become available in material icons, add them to this mime-to-icon mapping. + const mimeToIcon = {default: 'insert_drive_file', image: 'image', text: 'description', video: 'theaters'}; + + return mimeToIcon[mime] || mimeToIcon[(mime || '').split('/')[0]] || mimeToIcon['default']; +} + +export default class DocPreview extends React.PureComponent { + constructor(props) { + super(props); + + this.handleSendDoc = this.handleSendDoc.bind(this); + } + + handleSendDoc(caption) { + this.props.onClose(); + this.props.onSendMessage(this.props.content.file); + } + + render() { + if (!this.props.content) { + return null; + } + return ( +
+
+ {this.props.content.filename} + {e.preventDefault(); this.props.onClose();}}>close +
+
+
+ {iconFromMime(this.props.content.type)} +
{this.props.content.type || 'application/octet-stream'}
+
{bytesToHumanSize(this.props.content.size)}
+
+
+ +
+ ); + } +}; diff --git a/src/widgets/image-preview.jsx b/src/widgets/image-preview.jsx index dae65726..8232fc72 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'; @@ -13,6 +14,8 @@ export default class ImagePreview extends React.PureComponent { width: 0, height: 0 }; + + this.handleSendImage = this.handleSendImage.bind(this); } assignWidth(node) { @@ -25,6 +28,12 @@ export default class ImagePreview extends React.PureComponent { } } + handleSendImage(caption) { + this.props.onClose(); + this.props.onSendMessage(caption, this.props.content.type, this.props.content.bits, + this.props.content.width, this.props.content.height, this.props.content.filename); + } + render() { if (!this.props.content) { return null; @@ -41,39 +50,50 @@ export default class ImagePreview extends React.PureComponent { // Averate font aspect ratio is ~0.5; File name takes 1/3 of the viewport width. const maxlength = Math.max(((this.state.width / REM_SIZE / 1.5) | 0) - 2, 12); if (filename.length > maxlength) { - filename = filename.slice(0, maxlength/2 - 1) + '...' + filename.slice(1 - maxlength/2); + filename = filename.slice(0, maxlength/2 - 1) + '…' + filename.slice(1 - maxlength/2); } const width = this.props.content.width || '-'; const height = this.props.content.height || '-'; return ( -
+
- - file_download - + {!this.props.onSendMessage ? + + file_download + + : + {this.props.content.filename} + } {e.preventDefault(); this.props.onClose();}}>close
this.assignWidth(node)}>
- + {this.props.onSendMessage ? + + : + }
); } diff --git a/src/widgets/send-message.jsx b/src/widgets/send-message.jsx index a0f5025d..9f632bb6 100644 --- a/src/widgets/send-message.jsx +++ b/src/widgets/send-message.jsx @@ -3,9 +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 { bytesToHumanSize } from '../lib/strformat.js'; +import { KEYPRESS_DELAY } from '../config.js'; +import { filePasted } from '../lib/blob-helpers.js'; const messages = defineMessages({ 'messaging_disabled': { @@ -16,7 +15,12 @@ const messages = defineMessages({ 'type_new_message': { id: 'new_message_prompt', defaultMessage: 'New message', - description: 'Prompt in SendMessage in read-only topic' + description: 'Prompt in send message field' + }, + 'add_image_caption': { + id: 'image_caption_prompt', + defaultMessage: 'Image caption', + description: 'Prompt in SendMessage for attached image' }, 'file_attachment_too_large': { id: 'file_attachment_too_large', @@ -49,15 +53,21 @@ class SendMessage extends React.PureComponent { } componentDidMount() { - this.messageEditArea.addEventListener('paste', this.handlePasteEvent, false); + if (this.messageEditArea) { + this.messageEditArea.addEventListener('paste', this.handlePasteEvent, false); + } } componentWillUnmount() { - this.messageEditArea.removeEventListener('paste', this.handlePasteEvent, false); + if (this.messageEditArea) { + this.messageEditArea.removeEventListener('paste', this.handlePasteEvent, false); + } } componentDidUpdate() { - this.messageEditArea.focus(); + if (this.messageEditArea) { + this.messageEditArea.focus(); + } } handlePasteEvent(e) { @@ -67,11 +77,10 @@ class SendMessage extends React.PureComponent { // FIXME: handle large files too. if (filePasted(e, (bits, mime, width, height, fname) => { - this.props.sendMessage(Drafty.insertImage(null, - 0, mime, bits, width, height, fname)); + this.props.onAttachImage(mime, bits, width, height, fname); }, (mime, bits, fname) => { - this.props.sendMessage(Drafty.attachFile(null, mime, bits, fname)); + this.props.onAttachFile(mime, bits, fname); }, this.props.onError)) { @@ -82,34 +91,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.sendMessage(Drafty.insertImage(null, - 0, 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.sendMessage(Drafty.insertImage(null, - 0, 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 = ''; @@ -118,32 +100,7 @@ class SendMessage extends React.PureComponent { handleAttachFile(e) { const {formatMessage} = this.props.intl; if (e.target.files && e.target.files.length > 0) { - const file = e.target.files[0]; - if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) { - // Too large. - this.props.onError(formatMessage(messages.file_attachment_too_large, - {size: bytesToHumanSize(file.size), limit: bytesToHumanSize(MAX_EXTERN_ATTACHMENT_SIZE)}), 'err'); - } else if (file.size > MAX_INBAND_ATTACHMENT_SIZE) { - // Too large to send inband - uploading out of band and sending as a link. - const uploader = this.props.tinode.getLargeFileHelper(); - if (!uploader) { - this.props.onError(formatMessage(messages.cannot_initiate_upload)); - return; - } - // Format data and initiate upload. - const uploadCompletionPromise = uploader.upload(file); - const msg = Drafty.attachFile(null, file.type, null, file.name, file.size, uploadCompletionPromise); - // Pass data and the uploader to the TinodeWeb. - this.props.sendMessage(msg, uploadCompletionPromise, uploader); - } else { - // Small enough to send inband. - fileToBase64(file, - (mime, bits, fname) => { - this.props.sendMessage(Drafty.attachFile(null, mime, bits, fname)); - }, - this.props.onError - ); - } + this.props.onAttachFile(e.target.files[0]); } // Clear the value so the same file can be uploaded again. e.target.value = ''; @@ -152,8 +109,8 @@ class SendMessage extends React.PureComponent { handleSend(e) { e.preventDefault(); const message = this.state.message.trim(); - if (message) { - this.props.sendMessage(this.state.message.trim()); + if (message || this.props.acceptBlank || this.props.noInput) { + this.props.onSendMessage(message); this.setState({message: ''}); } } @@ -174,13 +131,12 @@ class SendMessage extends React.PureComponent { handleMessageTyping(e) { const newState = {message: e.target.value}; - const now = new Date().getTime(); - if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) { - const topic = this.props.tinode.getTopic(this.props.topic); - if (topic.isSubscribed()) { - topic.noteKeyPress(); + if (this.props.onKeyPress) { + const now = new Date().getTime(); + if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) { + this.props.onKeyPress(); + newState.keypressTimestamp = now; } - newState.keypressTimestamp = now; } this.setState(newState); } @@ -189,33 +145,44 @@ class SendMessage extends React.PureComponent { const {formatMessage} = this.props.intl; const prompt = this.props.disabled ? formatMessage(messages.messaging_disabled) : - formatMessage(messages.type_new_message); + (this.props.messagePrompt ? + formatMessage(messages[this.props.messagePrompt]) : + formatMessage(messages.type_new_message)); return (
+ {this.props.onAttachFile ? + (this.props.disabled ? + <> + photo + attach_file + + : + <> + {e.preventDefault(); this.attachImage.click();}} title="Add image"> + photo + + {e.preventDefault(); this.attachFile.click();}} title="Attach file"> + attach_file + + ) + : + null} + {this.props.noInput ? +
: +