| | @@ -701,11 +701,11 @@ |
| 701 | 701 | e = this.getMessageElemById(id); |
| 702 | 702 | } |
| 703 | 703 | if(!e || !id) return false; |
| 704 | 704 | else if(e.$isToggling) return; |
| 705 | 705 | e.$isToggling = true; |
| 706 | | - const content = e.querySelector('.message-widget-content'); |
| 706 | + const content = e.querySelector('.content-target'); |
| 707 | 707 | if(!content.$elems){ |
| 708 | 708 | content.$elems = [ |
| 709 | 709 | content.firstElementChild, // parsed elem |
| 710 | 710 | undefined // plaintext elem |
| 711 | 711 | ]; |
| | @@ -890,10 +890,21 @@ |
| 890 | 890 | d.getHours(),":", |
| 891 | 891 | (d.getMinutes()+100).toString().slice(1,3), |
| 892 | 892 | ' ', dowMap[d.getDay()] |
| 893 | 893 | ].join(''); |
| 894 | 894 | }; |
| 895 | + |
| 896 | + const canEmbedFile = function f(msg){ |
| 897 | + if(!f.$rx){ |
| 898 | + f.$rx = /\.((html?)|(txt))$/i; |
| 899 | + } |
| 900 | + return msg.fname && ( |
| 901 | + f.$rx.test(msg.fname) |
| 902 | + || (msg.fmime |
| 903 | + && msg.fmime.startsWith("image/")) |
| 904 | + ); |
| 905 | + }; |
| 895 | 906 | |
| 896 | 907 | cf.prototype = { |
| 897 | 908 | scrollIntoView: function(){ |
| 898 | 909 | this.e.content.scrollIntoView(); |
| 899 | 910 | }, |
| | @@ -936,27 +947,68 @@ |
| 936 | 947 | && Chat.settings.getBool('images-inline',true) |
| 937 | 948 | ){ |
| 938 | 949 | contentTarget.appendChild(D.img("chat-download/" + m.msgid)); |
| 939 | 950 | ds.hasImage = 1; |
| 940 | 951 | }else{ |
| 941 | | - const a = D.a( |
| 942 | | - window.fossil.rootPath+ |
| 943 | | - 'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname), |
| 952 | + // Add a download link. |
| 953 | + const downloadUri = window.fossil.rootPath+ |
| 954 | + 'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname); |
| 955 | + const w = D.addClass(D.div(), 'attachment-link'); |
| 956 | + const a = D.a(downloadUri, |
| 944 | 957 | // ^^^ add m.fname to URL to cause downloaded file to have that name. |
| 945 | 958 | "(" + m.fname + " " + m.fsize + " bytes)" |
| 946 | 959 | ) |
| 947 | 960 | D.attr(a,'target','_blank'); |
| 948 | | - contentTarget.appendChild(a); |
| 961 | + D.append(w, a); |
| 962 | + if(canEmbedFile(m)){ |
| 963 | + /* Add an option to embed HTML attachments in an iframe. The primary |
| 964 | + use case is attached diffs. */ |
| 965 | + D.addClass(contentTarget, 'wide'); |
| 966 | + const embedTarget = this.e.content; |
| 967 | + const self = this; |
| 968 | + const btnEmbed = D.attr(D.checkbox("1", false), 'id', |
| 969 | + 'embed-'+ds.msgid); |
| 970 | + const btnLabel = D.label(btnEmbed, "Embed"); |
| 971 | + btnEmbed.addEventListener('change',function(){ |
| 972 | + if(self.e.iframe){ |
| 973 | + if(btnEmbed.checked) D.removeClass(self.e.iframe, 'hidden'); |
| 974 | + else D.addClass(self.e.iframe, 'hidden'); |
| 975 | + return; |
| 976 | + } |
| 977 | + D.disable(btnEmbed); |
| 978 | + const iframe = self.e.iframe = document.createElement('iframe'); |
| 979 | + D.append(embedTarget, iframe); |
| 980 | + iframe.addEventListener('load', function(){ |
| 981 | + D.enable(btnEmbed); |
| 982 | + const body = iframe.contentWindow.document.querySelector('body'); |
| 983 | + if(body && !body.style.fontSize){ |
| 984 | + /** _Attempt_ to force the iframe to inherit the message's text size |
| 985 | + if the body has no explicit size set. On desktop systems |
| 986 | + the size is apparently being inherited in that case, but on mobile |
| 987 | + not. */ |
| 988 | + const cs = window.getComputedStyle(self.e.content); |
| 989 | + body.style.fontSize = cs.fontSize; |
| 990 | + } |
| 991 | + iframe.style.maxHeight = iframe.style.height |
| 992 | + = iframe.contentWindow.document.documentElement.scrollHeight + 'px'; |
| 993 | + }); |
| 994 | + iframe.setAttribute('src', downloadUri); |
| 995 | + }); |
| 996 | + D.append(w, btnEmbed, btnLabel); |
| 997 | + } |
| 998 | + contentTarget.appendChild(w); |
| 949 | 999 | } |
| 950 | 1000 | } |
| 951 | 1001 | if(m.xmsg){ |
| 952 | 1002 | if(m.fsize>0){ |
| 953 | 1003 | /* We have file/image content, so need another element for |
| 954 | 1004 | the message text. */ |
| 955 | 1005 | contentTarget = D.div(); |
| 956 | 1006 | D.append(this.e.content, contentTarget); |
| 957 | 1007 | } |
| 1008 | + D.addClass(contentTarget, 'content-target' |
| 1009 | + /*target element for the 'toggle text mode' feature*/); |
| 958 | 1010 | // The m.xmsg text comes from the same server as this script and |
| 959 | 1011 | // is guaranteed by that server to be "safe" HTML - safe in the |
| 960 | 1012 | // sense that it is not possible for a malefactor to inject HTML |
| 961 | 1013 | // or javascript or CSS. The m.xmsg content might contain |
| 962 | 1014 | // hyperlinks, but otherwise it will be markup-free. See the |
| 963 | 1015 | |