Fossil SCM

/chat: use the historical timestamp format for main-feed messages and ISO-8601 for search results. Misc. internal cleanups.

stephan 2024-07-02 09:22 fts5-chat-search
Commit 3c53bd325a371df5e1f75aa7a39ca12aca07a37bc34790c0875db0bb8b1f28a9
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -908,11 +908,11 @@
908908
Chat.MessageWidget = (function(){
909909
/**
910910
Constructor. If passed an argument, it is passed to
911911
this.setMessage() after initialization.
912912
*/
913
- const cf = function(){
913
+ const ctor = function(){
914914
this.e = {
915915
body: D.addClass(D.div(), 'message-widget'),
916916
tab: D.addClass(D.div(), 'message-widget-tab'),
917917
content: D.addClass(D.div(), 'message-widget-content')
918918
};
@@ -931,17 +931,16 @@
931931
};
932932
/* Given a Date, returns the timestamp string used in the "tab"
933933
part of message widgets. If longFmt is true then a verbose
934934
format is used, else a brief format is used. The returned string
935935
is in client-local time. */
936
- const theTime = function(d, longFmt=true){
936
+ const theTime = function(d, longFmt=false){
937937
const li = [];
938938
if( longFmt ){
939
- //li.push( d.toISOString() );
940939
li.push(
941940
d.getFullYear(),
942
- '-', pad2(d.getMonth()+1/*sigh*/),
941
+ '-', pad2(d.getMonth()+1),
943942
'-', pad2(d.getDate()),
944943
' ',
945944
d.getHours(), ":",
946945
(d.getMinutes()+100).toString().slice(1,3)
947946
);
@@ -961,19 +960,20 @@
961960
*/
962961
const canEmbedFile = function f(msg){
963962
if(!f.$rx){
964963
f.$rx = /\.((html?)|(txt)|(md)|(wiki)|(pikchr))$/i;
965964
f.$specificTypes = [
965
+ /* Mime types we know we can embed, sans image/... */
966966
'text/plain',
967967
'text/html',
968968
'text/x-markdown',
969969
/* Firefox sends text/markdown when uploading .md files */
970970
'text/markdown',
971971
'text/x-pikchr',
972972
'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. */
975975
];
976976
}
977977
if(msg.fmime){
978978
if(msg.fmime.startsWith("image/")
979979
|| f.$specificTypes.indexOf(msg.fmime)>=0){
@@ -987,20 +987,18 @@
987987
Returns true if the given message object "should"
988988
be embedded in fossil-rendered form instead of
989989
raw content form. This is only intended to be passed
990990
message objects for which canEmbedFile() returns true.
991991
*/
992
- const shouldWikiRenderEmbed = function f(msg){
992
+ const shouldFossilRenderEmbed = function f(msg){
993993
if(!f.$rx){
994994
f.$rx = /\.((md)|(wiki)|(pikchr))$/i;
995995
f.$specificTypes = [
996996
'text/x-markdown',
997997
'text/markdown' /* Firefox-uploaded md files */,
998998
'text/x-pikchr',
999999
'text/x-fossil-wiki'
1000
- // add more as we discover which ones Firefox won't
1001
- // force the user to try to download.
10021000
];
10031001
}
10041002
if(msg.fmime){
10051003
if(f.$specificTypes.indexOf(msg.fmime)>=0) return true;
10061004
}
@@ -1027,11 +1025,11 @@
10271025
= iframe.contentWindow.document.documentElement.scrollHeight + 'px';
10281026
if(isHidden) D.addClass(iframe, 'hidden');
10291027
}
10301028
};
10311029
1032
- cf.prototype = {
1030
+ ctor.prototype = {
10331031
scrollIntoView: function(){
10341032
this.e.content.scrollIntoView();
10351033
},
10361034
setMessage: function(m){
10371035
const ds = this.e.body.dataset;
@@ -1052,20 +1050,26 @@
10521050
var eXFrom /* element holding xfrom name */;
10531051
if(m.xfrom){
10541052
eXFrom = D.append(D.addClass(D.span(), 'xfrom'), m.xfrom);
10551053
const wrapper = D.append(
10561054
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
+ );
10581062
D.append(this.e.tab, wrapper);
10591063
}else{/*notification*/
10601064
D.addClass(this.e.body, 'notification');
10611065
if(m.isError){
10621066
D.addClass([contentTarget, this.e.tab], 'error');
10631067
}
10641068
D.append(
10651069
this.e.tab,
1066
- D.append(D.code(), 'notification @ ',theTime(d))
1070
+ D.append(D.code(), 'notification @ ',theTime(d,false))
10671071
);
10681072
}
10691073
if( m.xfrom && m.fsize>0 ){
10701074
if( m.fmime
10711075
&& m.fmime.startsWith("image/")
@@ -1088,18 +1092,18 @@
10881092
D.attr(a,'target','_blank');
10891093
D.append(w, a);
10901094
if(canEmbedFile(m)){
10911095
/* Add an option to embed HTML attachments in an iframe. The primary
10921096
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' : '';
10951099
D.addClass(contentTarget, 'wide');
10961100
const embedTarget = this.e.content;
10971101
const self = this;
10981102
const btnEmbed = D.attr(D.checkbox("1", false), 'id',
10991103
'embed-'+ds.msgid);
1100
- const btnLabel = D.label(btnEmbed, shouldWikiRender
1104
+ const btnLabel = D.label(btnEmbed, shouldFossilRender
11011105
? "Embed (fossil-rendered)" : "Embed");
11021106
/* Maintenance reminder: do not disable the toggle
11031107
button while the content is loading because that will
11041108
cause it to get stuck in disabled mode if the browser
11051109
decides that loading the content should prompt the
@@ -1304,11 +1308,11 @@
13041308
}/*end static init*/
13051309
const theMsg = findMessageWidgetParent(ev.target);
13061310
if(theMsg) f.popup.show(theMsg);
13071311
}/*_handleLegendClicked()*/
13081312
};
1309
- return cf;
1313
+ return ctor;
13101314
})()/*MessageWidget*/;
13111315
13121316
/**
13131317
A widget for loading more messages (context) around a /chat-query
13141318
result message.
@@ -1315,11 +1319,11 @@
13151319
*/
13161320
Chat.SearchCtxLoader = (function(){
13171321
const nMsgContext = 5;
13181322
const zUpArrow = '\u25B2';
13191323
const zDownArrow = '\u25BC';
1320
- const cf = function(o){
1324
+ const ctor = function(o){
13211325
13221326
/* iFirstInTable:
13231327
** msgid of first row in chatfts table.
13241328
**
13251329
** iLastInTable:
@@ -1364,11 +1368,11 @@
13641368
this.e.down.addEventListener('click', ()=>ms.load_messages(true));
13651369
this.e.all.addEventListener('click', ()=>ms.load_messages( (ms.o.iPrevId==0) ));
13661370
this.set_button_visibility();
13671371
};
13681372
1369
- cf.prototype = {
1373
+ ctor.prototype = {
13701374
set_button_visibility: function() {
13711375
if( !this.e ) return;
13721376
const o = this.o;
13731377
13741378
const iPrevId = (o.iPrevId!=0) ? o.iPrevId : o.iFirstInTable-1;
@@ -1473,11 +1477,11 @@
14731477
}
14741478
});
14751479
}
14761480
};
14771481
1478
- return cf;
1482
+ return ctor;
14791483
})() /*SearchCtxLoader*/;
14801484
14811485
const BlobXferState = (function(){
14821486
/* State for paste and drag/drop */
14831487
const bxs = {
@@ -2396,10 +2400,11 @@
23962400
responseType: 'json',
23972401
onload:function(jx){
23982402
let previd = 0;
23992403
D.clearElement(eMsgTgt);
24002404
jx.msgs.forEach((m)=>{
2405
+ m.isSearchResult = true;
24012406
const mw = new Chat.MessageWidget(m);
24022407
const spacer = new Chat.SearchCtxLoader({
24032408
first: jx.first,
24042409
last: jx.last,
24052410
previd: previd,
24062411
--- 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
--- src/style.chat.css
+++ src/style.chat.css
@@ -115,16 +115,27 @@
115115
white-space: nowrap;
116116
}
117117
body.chat .fossil-tooltip.help-buttonlet-content {
118118
font-size: 80%;
119119
}
120
+
121
+body.chat .message-widget .message-widget-tab {
122
+ /* Element which renders the main metadata for a given message. */
123
+}
120124
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 */
123126
font-style: italic;
124127
font-weight: bold;
125128
}
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
+}
126137
127138
body.chat .message-widget .match {
128139
font-weight: bold;
129140
background-color: yellow;
130141
}
131142
--- 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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button