Fossil SCM
/chat: use the historical timestamp format for main-feed messages and ISO-8601 for search results. Misc. internal cleanups.
Commit
3c53bd325a371df5e1f75aa7a39ca12aca07a37bc34790c0875db0bb8b1f28a9
Parent
417bb1506482b41…
2 files changed
+24
-19
+13
-2
+24
-19
| --- src/fossil.page.chat.js | ||
| +++ src/fossil.page.chat.js | ||
| @@ -908,11 +908,11 @@ | ||
| 908 | 908 | Chat.MessageWidget = (function(){ |
| 909 | 909 | /** |
| 910 | 910 | Constructor. If passed an argument, it is passed to |
| 911 | 911 | this.setMessage() after initialization. |
| 912 | 912 | */ |
| 913 | - const cf = function(){ | |
| 913 | + const ctor = function(){ | |
| 914 | 914 | this.e = { |
| 915 | 915 | body: D.addClass(D.div(), 'message-widget'), |
| 916 | 916 | tab: D.addClass(D.div(), 'message-widget-tab'), |
| 917 | 917 | content: D.addClass(D.div(), 'message-widget-content') |
| 918 | 918 | }; |
| @@ -931,17 +931,16 @@ | ||
| 931 | 931 | }; |
| 932 | 932 | /* Given a Date, returns the timestamp string used in the "tab" |
| 933 | 933 | part of message widgets. If longFmt is true then a verbose |
| 934 | 934 | format is used, else a brief format is used. The returned string |
| 935 | 935 | is in client-local time. */ |
| 936 | - const theTime = function(d, longFmt=true){ | |
| 936 | + const theTime = function(d, longFmt=false){ | |
| 937 | 937 | const li = []; |
| 938 | 938 | if( longFmt ){ |
| 939 | - //li.push( d.toISOString() ); | |
| 940 | 939 | li.push( |
| 941 | 940 | d.getFullYear(), |
| 942 | - '-', pad2(d.getMonth()+1/*sigh*/), | |
| 941 | + '-', pad2(d.getMonth()+1), | |
| 943 | 942 | '-', pad2(d.getDate()), |
| 944 | 943 | ' ', |
| 945 | 944 | d.getHours(), ":", |
| 946 | 945 | (d.getMinutes()+100).toString().slice(1,3) |
| 947 | 946 | ); |
| @@ -961,19 +960,20 @@ | ||
| 961 | 960 | */ |
| 962 | 961 | const canEmbedFile = function f(msg){ |
| 963 | 962 | if(!f.$rx){ |
| 964 | 963 | f.$rx = /\.((html?)|(txt)|(md)|(wiki)|(pikchr))$/i; |
| 965 | 964 | f.$specificTypes = [ |
| 965 | + /* Mime types we know we can embed, sans image/... */ | |
| 966 | 966 | 'text/plain', |
| 967 | 967 | 'text/html', |
| 968 | 968 | 'text/x-markdown', |
| 969 | 969 | /* Firefox sends text/markdown when uploading .md files */ |
| 970 | 970 | 'text/markdown', |
| 971 | 971 | 'text/x-pikchr', |
| 972 | 972 | 'text/x-fossil-wiki' |
| 973 | - // add more as we discover which ones Firefox won't | |
| 974 | - // force the user to try to download. | |
| 973 | + /* Add more as we discover which ones Firefox won't | |
| 974 | + force the user to try to download. */ | |
| 975 | 975 | ]; |
| 976 | 976 | } |
| 977 | 977 | if(msg.fmime){ |
| 978 | 978 | if(msg.fmime.startsWith("image/") |
| 979 | 979 | || f.$specificTypes.indexOf(msg.fmime)>=0){ |
| @@ -987,20 +987,18 @@ | ||
| 987 | 987 | Returns true if the given message object "should" |
| 988 | 988 | be embedded in fossil-rendered form instead of |
| 989 | 989 | raw content form. This is only intended to be passed |
| 990 | 990 | message objects for which canEmbedFile() returns true. |
| 991 | 991 | */ |
| 992 | - const shouldWikiRenderEmbed = function f(msg){ | |
| 992 | + const shouldFossilRenderEmbed = function f(msg){ | |
| 993 | 993 | if(!f.$rx){ |
| 994 | 994 | f.$rx = /\.((md)|(wiki)|(pikchr))$/i; |
| 995 | 995 | f.$specificTypes = [ |
| 996 | 996 | 'text/x-markdown', |
| 997 | 997 | 'text/markdown' /* Firefox-uploaded md files */, |
| 998 | 998 | 'text/x-pikchr', |
| 999 | 999 | 'text/x-fossil-wiki' |
| 1000 | - // add more as we discover which ones Firefox won't | |
| 1001 | - // force the user to try to download. | |
| 1002 | 1000 | ]; |
| 1003 | 1001 | } |
| 1004 | 1002 | if(msg.fmime){ |
| 1005 | 1003 | if(f.$specificTypes.indexOf(msg.fmime)>=0) return true; |
| 1006 | 1004 | } |
| @@ -1027,11 +1025,11 @@ | ||
| 1027 | 1025 | = iframe.contentWindow.document.documentElement.scrollHeight + 'px'; |
| 1028 | 1026 | if(isHidden) D.addClass(iframe, 'hidden'); |
| 1029 | 1027 | } |
| 1030 | 1028 | }; |
| 1031 | 1029 | |
| 1032 | - cf.prototype = { | |
| 1030 | + ctor.prototype = { | |
| 1033 | 1031 | scrollIntoView: function(){ |
| 1034 | 1032 | this.e.content.scrollIntoView(); |
| 1035 | 1033 | }, |
| 1036 | 1034 | setMessage: function(m){ |
| 1037 | 1035 | const ds = this.e.body.dataset; |
| @@ -1052,20 +1050,26 @@ | ||
| 1052 | 1050 | var eXFrom /* element holding xfrom name */; |
| 1053 | 1051 | if(m.xfrom){ |
| 1054 | 1052 | eXFrom = D.append(D.addClass(D.span(), 'xfrom'), m.xfrom); |
| 1055 | 1053 | const wrapper = D.append( |
| 1056 | 1054 | D.span(), eXFrom, |
| 1057 | - D.text(" #",(m.msgid||'???'),' ',theTime(d))) | |
| 1055 | + ' ', | |
| 1056 | + D.append(D.addClass(D.span(), 'msgid'), | |
| 1057 | + '#' + (m.msgid||'???')), | |
| 1058 | + (m.isSearchResult ? ' ' : ' @ '), | |
| 1059 | + D.append(D.addClass(D.span(), 'timestamp'), | |
| 1060 | + theTime(d,!!m.isSearchResult)) | |
| 1061 | + ); | |
| 1058 | 1062 | D.append(this.e.tab, wrapper); |
| 1059 | 1063 | }else{/*notification*/ |
| 1060 | 1064 | D.addClass(this.e.body, 'notification'); |
| 1061 | 1065 | if(m.isError){ |
| 1062 | 1066 | D.addClass([contentTarget, this.e.tab], 'error'); |
| 1063 | 1067 | } |
| 1064 | 1068 | D.append( |
| 1065 | 1069 | this.e.tab, |
| 1066 | - D.append(D.code(), 'notification @ ',theTime(d)) | |
| 1070 | + D.append(D.code(), 'notification @ ',theTime(d,false)) | |
| 1067 | 1071 | ); |
| 1068 | 1072 | } |
| 1069 | 1073 | if( m.xfrom && m.fsize>0 ){ |
| 1070 | 1074 | if( m.fmime |
| 1071 | 1075 | && m.fmime.startsWith("image/") |
| @@ -1088,18 +1092,18 @@ | ||
| 1088 | 1092 | D.attr(a,'target','_blank'); |
| 1089 | 1093 | D.append(w, a); |
| 1090 | 1094 | if(canEmbedFile(m)){ |
| 1091 | 1095 | /* Add an option to embed HTML attachments in an iframe. The primary |
| 1092 | 1096 | use case is attached diffs. */ |
| 1093 | - const shouldWikiRender = shouldWikiRenderEmbed(m); | |
| 1094 | - const downloadArgs = shouldWikiRender ? '?render' : ''; | |
| 1097 | + const shouldFossilRender = shouldFossilRenderEmbed(m); | |
| 1098 | + const downloadArgs = shouldFossilRender ? '?render' : ''; | |
| 1095 | 1099 | D.addClass(contentTarget, 'wide'); |
| 1096 | 1100 | const embedTarget = this.e.content; |
| 1097 | 1101 | const self = this; |
| 1098 | 1102 | const btnEmbed = D.attr(D.checkbox("1", false), 'id', |
| 1099 | 1103 | 'embed-'+ds.msgid); |
| 1100 | - const btnLabel = D.label(btnEmbed, shouldWikiRender | |
| 1104 | + const btnLabel = D.label(btnEmbed, shouldFossilRender | |
| 1101 | 1105 | ? "Embed (fossil-rendered)" : "Embed"); |
| 1102 | 1106 | /* Maintenance reminder: do not disable the toggle |
| 1103 | 1107 | button while the content is loading because that will |
| 1104 | 1108 | cause it to get stuck in disabled mode if the browser |
| 1105 | 1109 | decides that loading the content should prompt the |
| @@ -1304,11 +1308,11 @@ | ||
| 1304 | 1308 | }/*end static init*/ |
| 1305 | 1309 | const theMsg = findMessageWidgetParent(ev.target); |
| 1306 | 1310 | if(theMsg) f.popup.show(theMsg); |
| 1307 | 1311 | }/*_handleLegendClicked()*/ |
| 1308 | 1312 | }; |
| 1309 | - return cf; | |
| 1313 | + return ctor; | |
| 1310 | 1314 | })()/*MessageWidget*/; |
| 1311 | 1315 | |
| 1312 | 1316 | /** |
| 1313 | 1317 | A widget for loading more messages (context) around a /chat-query |
| 1314 | 1318 | result message. |
| @@ -1315,11 +1319,11 @@ | ||
| 1315 | 1319 | */ |
| 1316 | 1320 | Chat.SearchCtxLoader = (function(){ |
| 1317 | 1321 | const nMsgContext = 5; |
| 1318 | 1322 | const zUpArrow = '\u25B2'; |
| 1319 | 1323 | const zDownArrow = '\u25BC'; |
| 1320 | - const cf = function(o){ | |
| 1324 | + const ctor = function(o){ | |
| 1321 | 1325 | |
| 1322 | 1326 | /* iFirstInTable: |
| 1323 | 1327 | ** msgid of first row in chatfts table. |
| 1324 | 1328 | ** |
| 1325 | 1329 | ** iLastInTable: |
| @@ -1364,11 +1368,11 @@ | ||
| 1364 | 1368 | this.e.down.addEventListener('click', ()=>ms.load_messages(true)); |
| 1365 | 1369 | this.e.all.addEventListener('click', ()=>ms.load_messages( (ms.o.iPrevId==0) )); |
| 1366 | 1370 | this.set_button_visibility(); |
| 1367 | 1371 | }; |
| 1368 | 1372 | |
| 1369 | - cf.prototype = { | |
| 1373 | + ctor.prototype = { | |
| 1370 | 1374 | set_button_visibility: function() { |
| 1371 | 1375 | if( !this.e ) return; |
| 1372 | 1376 | const o = this.o; |
| 1373 | 1377 | |
| 1374 | 1378 | const iPrevId = (o.iPrevId!=0) ? o.iPrevId : o.iFirstInTable-1; |
| @@ -1473,11 +1477,11 @@ | ||
| 1473 | 1477 | } |
| 1474 | 1478 | }); |
| 1475 | 1479 | } |
| 1476 | 1480 | }; |
| 1477 | 1481 | |
| 1478 | - return cf; | |
| 1482 | + return ctor; | |
| 1479 | 1483 | })() /*SearchCtxLoader*/; |
| 1480 | 1484 | |
| 1481 | 1485 | const BlobXferState = (function(){ |
| 1482 | 1486 | /* State for paste and drag/drop */ |
| 1483 | 1487 | const bxs = { |
| @@ -2396,10 +2400,11 @@ | ||
| 2396 | 2400 | responseType: 'json', |
| 2397 | 2401 | onload:function(jx){ |
| 2398 | 2402 | let previd = 0; |
| 2399 | 2403 | D.clearElement(eMsgTgt); |
| 2400 | 2404 | jx.msgs.forEach((m)=>{ |
| 2405 | + m.isSearchResult = true; | |
| 2401 | 2406 | const mw = new Chat.MessageWidget(m); |
| 2402 | 2407 | const spacer = new Chat.SearchCtxLoader({ |
| 2403 | 2408 | first: jx.first, |
| 2404 | 2409 | last: jx.last, |
| 2405 | 2410 | previd: previd, |
| 2406 | 2411 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -908,11 +908,11 @@ | |
| 908 | Chat.MessageWidget = (function(){ |
| 909 | /** |
| 910 | Constructor. If passed an argument, it is passed to |
| 911 | this.setMessage() after initialization. |
| 912 | */ |
| 913 | const cf = function(){ |
| 914 | this.e = { |
| 915 | body: D.addClass(D.div(), 'message-widget'), |
| 916 | tab: D.addClass(D.div(), 'message-widget-tab'), |
| 917 | content: D.addClass(D.div(), 'message-widget-content') |
| 918 | }; |
| @@ -931,17 +931,16 @@ | |
| 931 | }; |
| 932 | /* Given a Date, returns the timestamp string used in the "tab" |
| 933 | part of message widgets. If longFmt is true then a verbose |
| 934 | format is used, else a brief format is used. The returned string |
| 935 | is in client-local time. */ |
| 936 | const theTime = function(d, longFmt=true){ |
| 937 | const li = []; |
| 938 | if( longFmt ){ |
| 939 | //li.push( d.toISOString() ); |
| 940 | li.push( |
| 941 | d.getFullYear(), |
| 942 | '-', pad2(d.getMonth()+1/*sigh*/), |
| 943 | '-', pad2(d.getDate()), |
| 944 | ' ', |
| 945 | d.getHours(), ":", |
| 946 | (d.getMinutes()+100).toString().slice(1,3) |
| 947 | ); |
| @@ -961,19 +960,20 @@ | |
| 961 | */ |
| 962 | const canEmbedFile = function f(msg){ |
| 963 | if(!f.$rx){ |
| 964 | f.$rx = /\.((html?)|(txt)|(md)|(wiki)|(pikchr))$/i; |
| 965 | f.$specificTypes = [ |
| 966 | 'text/plain', |
| 967 | 'text/html', |
| 968 | 'text/x-markdown', |
| 969 | /* Firefox sends text/markdown when uploading .md files */ |
| 970 | 'text/markdown', |
| 971 | 'text/x-pikchr', |
| 972 | 'text/x-fossil-wiki' |
| 973 | // add more as we discover which ones Firefox won't |
| 974 | // force the user to try to download. |
| 975 | ]; |
| 976 | } |
| 977 | if(msg.fmime){ |
| 978 | if(msg.fmime.startsWith("image/") |
| 979 | || f.$specificTypes.indexOf(msg.fmime)>=0){ |
| @@ -987,20 +987,18 @@ | |
| 987 | Returns true if the given message object "should" |
| 988 | be embedded in fossil-rendered form instead of |
| 989 | raw content form. This is only intended to be passed |
| 990 | message objects for which canEmbedFile() returns true. |
| 991 | */ |
| 992 | const shouldWikiRenderEmbed = function f(msg){ |
| 993 | if(!f.$rx){ |
| 994 | f.$rx = /\.((md)|(wiki)|(pikchr))$/i; |
| 995 | f.$specificTypes = [ |
| 996 | 'text/x-markdown', |
| 997 | 'text/markdown' /* Firefox-uploaded md files */, |
| 998 | 'text/x-pikchr', |
| 999 | 'text/x-fossil-wiki' |
| 1000 | // add more as we discover which ones Firefox won't |
| 1001 | // force the user to try to download. |
| 1002 | ]; |
| 1003 | } |
| 1004 | if(msg.fmime){ |
| 1005 | if(f.$specificTypes.indexOf(msg.fmime)>=0) return true; |
| 1006 | } |
| @@ -1027,11 +1025,11 @@ | |
| 1027 | = iframe.contentWindow.document.documentElement.scrollHeight + 'px'; |
| 1028 | if(isHidden) D.addClass(iframe, 'hidden'); |
| 1029 | } |
| 1030 | }; |
| 1031 | |
| 1032 | cf.prototype = { |
| 1033 | scrollIntoView: function(){ |
| 1034 | this.e.content.scrollIntoView(); |
| 1035 | }, |
| 1036 | setMessage: function(m){ |
| 1037 | const ds = this.e.body.dataset; |
| @@ -1052,20 +1050,26 @@ | |
| 1052 | var eXFrom /* element holding xfrom name */; |
| 1053 | if(m.xfrom){ |
| 1054 | eXFrom = D.append(D.addClass(D.span(), 'xfrom'), m.xfrom); |
| 1055 | const wrapper = D.append( |
| 1056 | D.span(), eXFrom, |
| 1057 | D.text(" #",(m.msgid||'???'),' ',theTime(d))) |
| 1058 | D.append(this.e.tab, wrapper); |
| 1059 | }else{/*notification*/ |
| 1060 | D.addClass(this.e.body, 'notification'); |
| 1061 | if(m.isError){ |
| 1062 | D.addClass([contentTarget, this.e.tab], 'error'); |
| 1063 | } |
| 1064 | D.append( |
| 1065 | this.e.tab, |
| 1066 | D.append(D.code(), 'notification @ ',theTime(d)) |
| 1067 | ); |
| 1068 | } |
| 1069 | if( m.xfrom && m.fsize>0 ){ |
| 1070 | if( m.fmime |
| 1071 | && m.fmime.startsWith("image/") |
| @@ -1088,18 +1092,18 @@ | |
| 1088 | D.attr(a,'target','_blank'); |
| 1089 | D.append(w, a); |
| 1090 | if(canEmbedFile(m)){ |
| 1091 | /* Add an option to embed HTML attachments in an iframe. The primary |
| 1092 | use case is attached diffs. */ |
| 1093 | const shouldWikiRender = shouldWikiRenderEmbed(m); |
| 1094 | const downloadArgs = shouldWikiRender ? '?render' : ''; |
| 1095 | D.addClass(contentTarget, 'wide'); |
| 1096 | const embedTarget = this.e.content; |
| 1097 | const self = this; |
| 1098 | const btnEmbed = D.attr(D.checkbox("1", false), 'id', |
| 1099 | 'embed-'+ds.msgid); |
| 1100 | const btnLabel = D.label(btnEmbed, shouldWikiRender |
| 1101 | ? "Embed (fossil-rendered)" : "Embed"); |
| 1102 | /* Maintenance reminder: do not disable the toggle |
| 1103 | button while the content is loading because that will |
| 1104 | cause it to get stuck in disabled mode if the browser |
| 1105 | decides that loading the content should prompt the |
| @@ -1304,11 +1308,11 @@ | |
| 1304 | }/*end static init*/ |
| 1305 | const theMsg = findMessageWidgetParent(ev.target); |
| 1306 | if(theMsg) f.popup.show(theMsg); |
| 1307 | }/*_handleLegendClicked()*/ |
| 1308 | }; |
| 1309 | return cf; |
| 1310 | })()/*MessageWidget*/; |
| 1311 | |
| 1312 | /** |
| 1313 | A widget for loading more messages (context) around a /chat-query |
| 1314 | result message. |
| @@ -1315,11 +1319,11 @@ | |
| 1315 | */ |
| 1316 | Chat.SearchCtxLoader = (function(){ |
| 1317 | const nMsgContext = 5; |
| 1318 | const zUpArrow = '\u25B2'; |
| 1319 | const zDownArrow = '\u25BC'; |
| 1320 | const cf = function(o){ |
| 1321 | |
| 1322 | /* iFirstInTable: |
| 1323 | ** msgid of first row in chatfts table. |
| 1324 | ** |
| 1325 | ** iLastInTable: |
| @@ -1364,11 +1368,11 @@ | |
| 1364 | this.e.down.addEventListener('click', ()=>ms.load_messages(true)); |
| 1365 | this.e.all.addEventListener('click', ()=>ms.load_messages( (ms.o.iPrevId==0) )); |
| 1366 | this.set_button_visibility(); |
| 1367 | }; |
| 1368 | |
| 1369 | cf.prototype = { |
| 1370 | set_button_visibility: function() { |
| 1371 | if( !this.e ) return; |
| 1372 | const o = this.o; |
| 1373 | |
| 1374 | const iPrevId = (o.iPrevId!=0) ? o.iPrevId : o.iFirstInTable-1; |
| @@ -1473,11 +1477,11 @@ | |
| 1473 | } |
| 1474 | }); |
| 1475 | } |
| 1476 | }; |
| 1477 | |
| 1478 | return cf; |
| 1479 | })() /*SearchCtxLoader*/; |
| 1480 | |
| 1481 | const BlobXferState = (function(){ |
| 1482 | /* State for paste and drag/drop */ |
| 1483 | const bxs = { |
| @@ -2396,10 +2400,11 @@ | |
| 2396 | responseType: 'json', |
| 2397 | onload:function(jx){ |
| 2398 | let previd = 0; |
| 2399 | D.clearElement(eMsgTgt); |
| 2400 | jx.msgs.forEach((m)=>{ |
| 2401 | const mw = new Chat.MessageWidget(m); |
| 2402 | const spacer = new Chat.SearchCtxLoader({ |
| 2403 | first: jx.first, |
| 2404 | last: jx.last, |
| 2405 | previd: previd, |
| 2406 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -908,11 +908,11 @@ | |
| 908 | Chat.MessageWidget = (function(){ |
| 909 | /** |
| 910 | Constructor. If passed an argument, it is passed to |
| 911 | this.setMessage() after initialization. |
| 912 | */ |
| 913 | const ctor = function(){ |
| 914 | this.e = { |
| 915 | body: D.addClass(D.div(), 'message-widget'), |
| 916 | tab: D.addClass(D.div(), 'message-widget-tab'), |
| 917 | content: D.addClass(D.div(), 'message-widget-content') |
| 918 | }; |
| @@ -931,17 +931,16 @@ | |
| 931 | }; |
| 932 | /* Given a Date, returns the timestamp string used in the "tab" |
| 933 | part of message widgets. If longFmt is true then a verbose |
| 934 | format is used, else a brief format is used. The returned string |
| 935 | is in client-local time. */ |
| 936 | const theTime = function(d, longFmt=false){ |
| 937 | const li = []; |
| 938 | if( longFmt ){ |
| 939 | li.push( |
| 940 | d.getFullYear(), |
| 941 | '-', pad2(d.getMonth()+1), |
| 942 | '-', pad2(d.getDate()), |
| 943 | ' ', |
| 944 | d.getHours(), ":", |
| 945 | (d.getMinutes()+100).toString().slice(1,3) |
| 946 | ); |
| @@ -961,19 +960,20 @@ | |
| 960 | */ |
| 961 | const canEmbedFile = function f(msg){ |
| 962 | if(!f.$rx){ |
| 963 | f.$rx = /\.((html?)|(txt)|(md)|(wiki)|(pikchr))$/i; |
| 964 | f.$specificTypes = [ |
| 965 | /* Mime types we know we can embed, sans image/... */ |
| 966 | 'text/plain', |
| 967 | 'text/html', |
| 968 | 'text/x-markdown', |
| 969 | /* Firefox sends text/markdown when uploading .md files */ |
| 970 | 'text/markdown', |
| 971 | 'text/x-pikchr', |
| 972 | 'text/x-fossil-wiki' |
| 973 | /* Add more as we discover which ones Firefox won't |
| 974 | force the user to try to download. */ |
| 975 | ]; |
| 976 | } |
| 977 | if(msg.fmime){ |
| 978 | if(msg.fmime.startsWith("image/") |
| 979 | || f.$specificTypes.indexOf(msg.fmime)>=0){ |
| @@ -987,20 +987,18 @@ | |
| 987 | Returns true if the given message object "should" |
| 988 | be embedded in fossil-rendered form instead of |
| 989 | raw content form. This is only intended to be passed |
| 990 | message objects for which canEmbedFile() returns true. |
| 991 | */ |
| 992 | const shouldFossilRenderEmbed = function f(msg){ |
| 993 | if(!f.$rx){ |
| 994 | f.$rx = /\.((md)|(wiki)|(pikchr))$/i; |
| 995 | f.$specificTypes = [ |
| 996 | 'text/x-markdown', |
| 997 | 'text/markdown' /* Firefox-uploaded md files */, |
| 998 | 'text/x-pikchr', |
| 999 | 'text/x-fossil-wiki' |
| 1000 | ]; |
| 1001 | } |
| 1002 | if(msg.fmime){ |
| 1003 | if(f.$specificTypes.indexOf(msg.fmime)>=0) return true; |
| 1004 | } |
| @@ -1027,11 +1025,11 @@ | |
| 1025 | = iframe.contentWindow.document.documentElement.scrollHeight + 'px'; |
| 1026 | if(isHidden) D.addClass(iframe, 'hidden'); |
| 1027 | } |
| 1028 | }; |
| 1029 | |
| 1030 | ctor.prototype = { |
| 1031 | scrollIntoView: function(){ |
| 1032 | this.e.content.scrollIntoView(); |
| 1033 | }, |
| 1034 | setMessage: function(m){ |
| 1035 | const ds = this.e.body.dataset; |
| @@ -1052,20 +1050,26 @@ | |
| 1050 | var eXFrom /* element holding xfrom name */; |
| 1051 | if(m.xfrom){ |
| 1052 | eXFrom = D.append(D.addClass(D.span(), 'xfrom'), m.xfrom); |
| 1053 | const wrapper = D.append( |
| 1054 | D.span(), eXFrom, |
| 1055 | ' ', |
| 1056 | D.append(D.addClass(D.span(), 'msgid'), |
| 1057 | '#' + (m.msgid||'???')), |
| 1058 | (m.isSearchResult ? ' ' : ' @ '), |
| 1059 | D.append(D.addClass(D.span(), 'timestamp'), |
| 1060 | theTime(d,!!m.isSearchResult)) |
| 1061 | ); |
| 1062 | D.append(this.e.tab, wrapper); |
| 1063 | }else{/*notification*/ |
| 1064 | D.addClass(this.e.body, 'notification'); |
| 1065 | if(m.isError){ |
| 1066 | D.addClass([contentTarget, this.e.tab], 'error'); |
| 1067 | } |
| 1068 | D.append( |
| 1069 | this.e.tab, |
| 1070 | D.append(D.code(), 'notification @ ',theTime(d,false)) |
| 1071 | ); |
| 1072 | } |
| 1073 | if( m.xfrom && m.fsize>0 ){ |
| 1074 | if( m.fmime |
| 1075 | && m.fmime.startsWith("image/") |
| @@ -1088,18 +1092,18 @@ | |
| 1092 | D.attr(a,'target','_blank'); |
| 1093 | D.append(w, a); |
| 1094 | if(canEmbedFile(m)){ |
| 1095 | /* Add an option to embed HTML attachments in an iframe. The primary |
| 1096 | use case is attached diffs. */ |
| 1097 | const shouldFossilRender = shouldFossilRenderEmbed(m); |
| 1098 | const downloadArgs = shouldFossilRender ? '?render' : ''; |
| 1099 | D.addClass(contentTarget, 'wide'); |
| 1100 | const embedTarget = this.e.content; |
| 1101 | const self = this; |
| 1102 | const btnEmbed = D.attr(D.checkbox("1", false), 'id', |
| 1103 | 'embed-'+ds.msgid); |
| 1104 | const btnLabel = D.label(btnEmbed, shouldFossilRender |
| 1105 | ? "Embed (fossil-rendered)" : "Embed"); |
| 1106 | /* Maintenance reminder: do not disable the toggle |
| 1107 | button while the content is loading because that will |
| 1108 | cause it to get stuck in disabled mode if the browser |
| 1109 | decides that loading the content should prompt the |
| @@ -1304,11 +1308,11 @@ | |
| 1308 | }/*end static init*/ |
| 1309 | const theMsg = findMessageWidgetParent(ev.target); |
| 1310 | if(theMsg) f.popup.show(theMsg); |
| 1311 | }/*_handleLegendClicked()*/ |
| 1312 | }; |
| 1313 | return ctor; |
| 1314 | })()/*MessageWidget*/; |
| 1315 | |
| 1316 | /** |
| 1317 | A widget for loading more messages (context) around a /chat-query |
| 1318 | result message. |
| @@ -1315,11 +1319,11 @@ | |
| 1319 | */ |
| 1320 | Chat.SearchCtxLoader = (function(){ |
| 1321 | const nMsgContext = 5; |
| 1322 | const zUpArrow = '\u25B2'; |
| 1323 | const zDownArrow = '\u25BC'; |
| 1324 | const ctor = function(o){ |
| 1325 | |
| 1326 | /* iFirstInTable: |
| 1327 | ** msgid of first row in chatfts table. |
| 1328 | ** |
| 1329 | ** iLastInTable: |
| @@ -1364,11 +1368,11 @@ | |
| 1368 | this.e.down.addEventListener('click', ()=>ms.load_messages(true)); |
| 1369 | this.e.all.addEventListener('click', ()=>ms.load_messages( (ms.o.iPrevId==0) )); |
| 1370 | this.set_button_visibility(); |
| 1371 | }; |
| 1372 | |
| 1373 | ctor.prototype = { |
| 1374 | set_button_visibility: function() { |
| 1375 | if( !this.e ) return; |
| 1376 | const o = this.o; |
| 1377 | |
| 1378 | const iPrevId = (o.iPrevId!=0) ? o.iPrevId : o.iFirstInTable-1; |
| @@ -1473,11 +1477,11 @@ | |
| 1477 | } |
| 1478 | }); |
| 1479 | } |
| 1480 | }; |
| 1481 | |
| 1482 | return ctor; |
| 1483 | })() /*SearchCtxLoader*/; |
| 1484 | |
| 1485 | const BlobXferState = (function(){ |
| 1486 | /* State for paste and drag/drop */ |
| 1487 | const bxs = { |
| @@ -2396,10 +2400,11 @@ | |
| 2400 | responseType: 'json', |
| 2401 | onload:function(jx){ |
| 2402 | let previd = 0; |
| 2403 | D.clearElement(eMsgTgt); |
| 2404 | jx.msgs.forEach((m)=>{ |
| 2405 | m.isSearchResult = true; |
| 2406 | const mw = new Chat.MessageWidget(m); |
| 2407 | const spacer = new Chat.SearchCtxLoader({ |
| 2408 | first: jx.first, |
| 2409 | last: jx.last, |
| 2410 | previd: previd, |
| 2411 |
+13
-2
| --- src/style.chat.css | ||
| +++ src/style.chat.css | ||
| @@ -115,16 +115,27 @@ | ||
| 115 | 115 | white-space: nowrap; |
| 116 | 116 | } |
| 117 | 117 | body.chat .fossil-tooltip.help-buttonlet-content { |
| 118 | 118 | font-size: 80%; |
| 119 | 119 | } |
| 120 | + | |
| 121 | +body.chat .message-widget .message-widget-tab { | |
| 122 | + /* Element which renders the main metadata for a given message. */ | |
| 123 | +} | |
| 120 | 124 | body.chat .message-widget .message-widget-tab .xfrom { |
| 121 | - /* Element which holds the "this message is from user X" part | |
| 122 | - of the message banner. */ | |
| 125 | + /* xfrom part of the message tab */ | |
| 123 | 126 | font-style: italic; |
| 124 | 127 | font-weight: bold; |
| 125 | 128 | } |
| 129 | + | |
| 130 | +body.chat .message-widget .message-widget-tab .mtime { | |
| 131 | + /* mtime part of the message tab */ | |
| 132 | +} | |
| 133 | + | |
| 134 | +body.chat .message-widget .message-widget-tab .msgid { | |
| 135 | + /* msgid part of the message tab */ | |
| 136 | +} | |
| 126 | 137 | |
| 127 | 138 | body.chat .message-widget .match { |
| 128 | 139 | font-weight: bold; |
| 129 | 140 | background-color: yellow; |
| 130 | 141 | } |
| 131 | 142 |
| --- src/style.chat.css | |
| +++ src/style.chat.css | |
| @@ -115,16 +115,27 @@ | |
| 115 | white-space: nowrap; |
| 116 | } |
| 117 | body.chat .fossil-tooltip.help-buttonlet-content { |
| 118 | font-size: 80%; |
| 119 | } |
| 120 | body.chat .message-widget .message-widget-tab .xfrom { |
| 121 | /* Element which holds the "this message is from user X" part |
| 122 | of the message banner. */ |
| 123 | font-style: italic; |
| 124 | font-weight: bold; |
| 125 | } |
| 126 | |
| 127 | body.chat .message-widget .match { |
| 128 | font-weight: bold; |
| 129 | background-color: yellow; |
| 130 | } |
| 131 |
| --- src/style.chat.css | |
| +++ src/style.chat.css | |
| @@ -115,16 +115,27 @@ | |
| 115 | white-space: nowrap; |
| 116 | } |
| 117 | body.chat .fossil-tooltip.help-buttonlet-content { |
| 118 | font-size: 80%; |
| 119 | } |
| 120 | |
| 121 | body.chat .message-widget .message-widget-tab { |
| 122 | /* Element which renders the main metadata for a given message. */ |
| 123 | } |
| 124 | body.chat .message-widget .message-widget-tab .xfrom { |
| 125 | /* xfrom part of the message tab */ |
| 126 | font-style: italic; |
| 127 | font-weight: bold; |
| 128 | } |
| 129 | |
| 130 | body.chat .message-widget .message-widget-tab .mtime { |
| 131 | /* mtime part of the message tab */ |
| 132 | } |
| 133 | |
| 134 | body.chat .message-widget .message-widget-tab .msgid { |
| 135 | /* msgid part of the message tab */ |
| 136 | } |
| 137 | |
| 138 | body.chat .message-widget .match { |
| 139 | font-weight: bold; |
| 140 | background-color: yellow; |
| 141 | } |
| 142 |