Fossil SCM

Add context-loading buttons to /chat search.

stephan 2024-06-30 15:44 UTC fts5-chat-search
Commit 884214d0e3d6e0c54ba3f0127a67496fcc693919427b67db21a0e1658c957029
2 files changed +1 -2 +169 -7
+1 -2
--- src/chat.c
+++ src/chat.c
@@ -244,12 +244,11 @@
244244
@ <div id='chat-config-options'></div>
245245
/* ^^^populated client-side */
246246
@ <div class='button-bar'><button class='action-close'>Close Settings</button></div>
247247
@ </div>
248248
@ <div id='chat-search' class='hidden chat-view'>
249
- @ <header>Chat history search...</header>
250
- @ <div id='chat-search-body' class='message-widget-content'></div>
249
+ @ <div class='message-widget-content'></div>
251250
/* ^^^populated client-side */
252251
@ <div class='button-bar'><button class='action-close'>Close Search</button></div>
253252
@ </div>
254253
@ <div id='chat-messages-wrapper' class='chat-view'>
255254
/* New chat messages get inserted immediately after this element */
256255
--- src/chat.c
+++ src/chat.c
@@ -244,12 +244,11 @@
244 @ <div id='chat-config-options'></div>
245 /* ^^^populated client-side */
246 @ <div class='button-bar'><button class='action-close'>Close Settings</button></div>
247 @ </div>
248 @ <div id='chat-search' class='hidden chat-view'>
249 @ <header>Chat history search...</header>
250 @ <div id='chat-search-body' class='message-widget-content'></div>
251 /* ^^^populated client-side */
252 @ <div class='button-bar'><button class='action-close'>Close Search</button></div>
253 @ </div>
254 @ <div id='chat-messages-wrapper' class='chat-view'>
255 /* New chat messages get inserted immediately after this element */
256
--- src/chat.c
+++ src/chat.c
@@ -244,12 +244,11 @@
244 @ <div id='chat-config-options'></div>
245 /* ^^^populated client-side */
246 @ <div class='button-bar'><button class='action-close'>Close Settings</button></div>
247 @ </div>
248 @ <div id='chat-search' class='hidden chat-view'>
249 @ <div class='message-widget-content'></div>
 
250 /* ^^^populated client-side */
251 @ <div class='button-bar'><button class='action-close'>Close Search</button></div>
252 @ </div>
253 @ <div id='chat-messages-wrapper' class='chat-view'>
254 /* New chat messages get inserted immediately after this element */
255
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -1011,11 +1011,11 @@
10111011
iframe.style.maxHeight = iframe.style.height
10121012
= iframe.contentWindow.document.documentElement.scrollHeight + 'px';
10131013
if(isHidden) D.addClass(iframe, 'hidden');
10141014
}
10151015
};
1016
-
1016
+
10171017
cf.prototype = {
10181018
scrollIntoView: function(){
10191019
this.e.content.scrollIntoView();
10201020
},
10211021
setMessage: function(m){
@@ -1292,10 +1292,160 @@
12921292
}/*_handleLegendClicked()*/
12931293
};
12941294
return cf;
12951295
})()/*MessageWidget*/;
12961296
1297
+ /**
1298
+ A widget for loading more messages (context) around a /chat-query
1299
+ result message.
1300
+ */
1301
+ Chat.SearchCtxLoader = (function(){
1302
+ const nMsgContext = 5;
1303
+ const zUpArrow = '\u25B2';
1304
+ const zDownArrow = '\u25BC';
1305
+ const cf = function(o){
1306
+
1307
+ /* iFirstInTable:
1308
+ ** msgid of first row in chatfts table.
1309
+ **
1310
+ ** iLastInTable:
1311
+ ** msgid of last row in chatfts table.
1312
+ **
1313
+ ** iPrevId:
1314
+ ** msgid of message immediately above this spacer. Or 0 if this
1315
+ ** spacer is above all results.
1316
+ **
1317
+ ** iNextId:
1318
+ ** msgid of message immediately below this spacer. Or 0 if this
1319
+ ** spacer is below all results.
1320
+ **
1321
+ ** bIgnoreClick:
1322
+ ** ignore any clicks if this is true. This is used to ensure there
1323
+ ** is only ever one request belonging to this widget outstanding
1324
+ ** at any time.
1325
+ */
1326
+ this.o = {
1327
+ iFirstInTable: o.first,
1328
+ iLastInTable: o.last,
1329
+ iPrevId: o.previd,
1330
+ iNextId: o.nextid,
1331
+ bIgnoreClick: false,
1332
+ };
1333
+
1334
+ this.e = {
1335
+ body: D.addClass(D.div(), 'spacer-widget'),
1336
+
1337
+ above: D.addClass(D.div(), 'spacer-widget-above'),
1338
+ buttons: D.addClass(D.div(), 'spacer-widget-buttons'),
1339
+ below: D.addClass(D.div(), 'spacer-widget-below'),
1340
+
1341
+ up: D.addClass(
1342
+ D.button(zDownArrow+' Load '+nMsgContext+' more '+zDownArrow),
1343
+ 'up'
1344
+ ),
1345
+ down: D.addClass(
1346
+ D.button(zUpArrow+' Load '+nMsgContext+' more '+zUpArrow),
1347
+ 'down'
1348
+ ),
1349
+ all: D.addClass(D.button('Load More'), 'all')
1350
+ };
1351
+
1352
+ D.append(this.e.buttons, this.e.up, this.e.down, this.e.all);
1353
+ D.append(this.e.body, this.e.above, this.e.buttons, this.e.below);
1354
+
1355
+ const ms = this;
1356
+ this.e.up.addEventListener('click', ()=>ms.load_messages(false));
1357
+ this.e.down.addEventListener('click', ()=>ms.load_messages(true));
1358
+ this.e.all.addEventListener('click', ()=>ms.load_messages( (ms.o.iPrevId==0) ));
1359
+ this.set_button_visibility();
1360
+ };
1361
+
1362
+ cf.prototype = {
1363
+ set_button_visibility: function() {
1364
+ const o = this.o;
1365
+
1366
+ const iPrevId = (o.iPrevId!=0) ? o.iPrevId : o.iFirstInTable-1;
1367
+ const iNextId = (o.iNextId!=0) ? o.iNextId : o.iLastInTable+1;
1368
+ var nDiff = (iNextId - iPrevId) - 1;
1369
+
1370
+ D.addClass([this.e.up, this.e.down, this.e.all], 'hidden');
1371
+
1372
+ if( nDiff>0 ){
1373
+
1374
+ if( nDiff>nMsgContext && (o.iPrevId==0 || o.iNextId==0) ){
1375
+ nDiff = nMsgContext;
1376
+ }
1377
+
1378
+ if( nDiff<=nMsgContext && o.iPrevId!=0 && o.iNextId!=0 ){
1379
+ D.removeClass(this.e.all, 'hidden');
1380
+ this.e.all.innerText = (
1381
+ zUpArrow + " Load " + nDiff + " more " + zDownArrow
1382
+ );
1383
+ }else{
1384
+ if( o.iPrevId!=0 ) D.removeClass(this.e.up, 'hidden');
1385
+ if( o.iNextId!=0 ) D.removeClass(this.e.down, 'hidden');
1386
+ }
1387
+ }
1388
+ },
1389
+
1390
+ load_messages: function(bDown) {
1391
+ if( this.bIgnoreClick ) return;
1392
+
1393
+ var iFirst = 0; /* msgid of first message to fetch */
1394
+ var nFetch = 0; /* Number of messages to fetch */
1395
+ var iEof = 0; /* last msgid in spacers range, plus 1 */
1396
+
1397
+ const e = this.e, o = this.o;
1398
+ this.bIgnoreClick = true;
1399
+
1400
+ /* Figure out the required range of messages. */
1401
+ if( bDown ){
1402
+ iFirst = this.o.iNextId - nMsgContext;
1403
+ if( iFirst<this.o.iFirstInTable ){
1404
+ iFirst = this.o.iFirstInTable;
1405
+ }
1406
+ }else{
1407
+ iFirst = this.o.iPrevId+1;
1408
+ }
1409
+ nFetch = nMsgContext;
1410
+ iEof = (this.o.iNextId > 0) ? this.o.iNextId : this.o.iLastInTable+1;
1411
+ if( iFirst+nFetch>iEof ){
1412
+ nFetch = iEof - iFirst;
1413
+ }
1414
+ const ms = this;
1415
+ F.fetch("chat-query",{
1416
+ urlParams:{
1417
+ q: '',
1418
+ n: nFetch,
1419
+ i: iFirst
1420
+ },
1421
+ responseType: "json",
1422
+ onload:function(jx){
1423
+ const firstChildOfBelow = e.below.firstChild;
1424
+ jx.msgs.forEach((m) => {
1425
+ var mw = new Chat.MessageWidget(m);
1426
+ if( bDown ){
1427
+ e.below.insertBefore(mw.e.body, firstChildOfBelow);
1428
+ }else{
1429
+ D.append(e.above, mw.e.body);
1430
+ }
1431
+ });
1432
+ if( bDown ){
1433
+ o.iNextId -= jx.msgs.length;
1434
+ }else{
1435
+ o.iPrevId += jx.msgs.length;
1436
+ }
1437
+ ms.set_button_visibility();
1438
+ ms.bIgnoreClick = false;
1439
+ }
1440
+ });
1441
+ }
1442
+ };
1443
+
1444
+ return cf;
1445
+ })() /*SearchCtxLoader*/;
1446
+
12971447
const BlobXferState = (function(){
12981448
/* State for paste and drag/drop */
12991449
const bxs = {
13001450
dropDetails: document.querySelector('#chat-drop-details'),
13011451
blob: undefined,
@@ -2173,28 +2323,40 @@
21732323
*/
21742324
Chat.submitSearch = function(){
21752325
const term = this.inputValue(true);
21762326
const eMsgTgt = D.clearElement(
21772327
this.e.viewSearch.querySelector('.message-widget-content')
2178
- );
2328
+ )
21792329
if( !term ) return;
2180
- D.append(eMsgTgt, "TODO: search term = ", term);
2330
+ D.append( eMsgTgt, "Searching for ",term," ...");
21812331
F.fetch(
21822332
"chat-query", {
21832333
urlParams: {q: term},
21842334
responseType: 'json',
21852335
onload:function(jx){
2186
- let prevId = 0;
2336
+ let previd = 0;
21872337
console.log("jx =",jx);
21882338
D.clearElement(eMsgTgt);
21892339
jx.msgs.forEach((m)=>{
21902340
const mw = new Chat.MessageWidget(m);
2191
- D.append( eMsgTgt, mw.e.body );
2192
- prevId = m.msgid;
2341
+ const spacer = new Chat.SearchCtxLoader({
2342
+ first: jx.first,
2343
+ last: jx.last,
2344
+ previd: previd,
2345
+ nextid: m.msgid
2346
+ });
2347
+ D.append( eMsgTgt, spacer.e.body, mw.e.body );
2348
+ previd = m.msgid;
21932349
});
21942350
if( jx.msgs.length ){
2195
- // TODO: MessageSpacer
2351
+ const spacer = new Chat.SearchCtxLoader({
2352
+ first: jx.first,
2353
+ last: jx.last,
2354
+ previd: previd,
2355
+ nextid: 0
2356
+ });
2357
+ D.append( eMsgTgt, spacer.e.body );
21962358
}else{
21972359
D.append( D.clearElement(eMsgTgt),
21982360
'No results matching the search term: ',
21992361
term );
22002362
}
22012363
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -1011,11 +1011,11 @@
1011 iframe.style.maxHeight = iframe.style.height
1012 = iframe.contentWindow.document.documentElement.scrollHeight + 'px';
1013 if(isHidden) D.addClass(iframe, 'hidden');
1014 }
1015 };
1016
1017 cf.prototype = {
1018 scrollIntoView: function(){
1019 this.e.content.scrollIntoView();
1020 },
1021 setMessage: function(m){
@@ -1292,10 +1292,160 @@
1292 }/*_handleLegendClicked()*/
1293 };
1294 return cf;
1295 })()/*MessageWidget*/;
1296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1297 const BlobXferState = (function(){
1298 /* State for paste and drag/drop */
1299 const bxs = {
1300 dropDetails: document.querySelector('#chat-drop-details'),
1301 blob: undefined,
@@ -2173,28 +2323,40 @@
2173 */
2174 Chat.submitSearch = function(){
2175 const term = this.inputValue(true);
2176 const eMsgTgt = D.clearElement(
2177 this.e.viewSearch.querySelector('.message-widget-content')
2178 );
2179 if( !term ) return;
2180 D.append(eMsgTgt, "TODO: search term = ", term);
2181 F.fetch(
2182 "chat-query", {
2183 urlParams: {q: term},
2184 responseType: 'json',
2185 onload:function(jx){
2186 let prevId = 0;
2187 console.log("jx =",jx);
2188 D.clearElement(eMsgTgt);
2189 jx.msgs.forEach((m)=>{
2190 const mw = new Chat.MessageWidget(m);
2191 D.append( eMsgTgt, mw.e.body );
2192 prevId = m.msgid;
 
 
 
 
 
 
2193 });
2194 if( jx.msgs.length ){
2195 // TODO: MessageSpacer
 
 
 
 
 
 
2196 }else{
2197 D.append( D.clearElement(eMsgTgt),
2198 'No results matching the search term: ',
2199 term );
2200 }
2201
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -1011,11 +1011,11 @@
1011 iframe.style.maxHeight = iframe.style.height
1012 = iframe.contentWindow.document.documentElement.scrollHeight + 'px';
1013 if(isHidden) D.addClass(iframe, 'hidden');
1014 }
1015 };
1016
1017 cf.prototype = {
1018 scrollIntoView: function(){
1019 this.e.content.scrollIntoView();
1020 },
1021 setMessage: function(m){
@@ -1292,10 +1292,160 @@
1292 }/*_handleLegendClicked()*/
1293 };
1294 return cf;
1295 })()/*MessageWidget*/;
1296
1297 /**
1298 A widget for loading more messages (context) around a /chat-query
1299 result message.
1300 */
1301 Chat.SearchCtxLoader = (function(){
1302 const nMsgContext = 5;
1303 const zUpArrow = '\u25B2';
1304 const zDownArrow = '\u25BC';
1305 const cf = function(o){
1306
1307 /* iFirstInTable:
1308 ** msgid of first row in chatfts table.
1309 **
1310 ** iLastInTable:
1311 ** msgid of last row in chatfts table.
1312 **
1313 ** iPrevId:
1314 ** msgid of message immediately above this spacer. Or 0 if this
1315 ** spacer is above all results.
1316 **
1317 ** iNextId:
1318 ** msgid of message immediately below this spacer. Or 0 if this
1319 ** spacer is below all results.
1320 **
1321 ** bIgnoreClick:
1322 ** ignore any clicks if this is true. This is used to ensure there
1323 ** is only ever one request belonging to this widget outstanding
1324 ** at any time.
1325 */
1326 this.o = {
1327 iFirstInTable: o.first,
1328 iLastInTable: o.last,
1329 iPrevId: o.previd,
1330 iNextId: o.nextid,
1331 bIgnoreClick: false,
1332 };
1333
1334 this.e = {
1335 body: D.addClass(D.div(), 'spacer-widget'),
1336
1337 above: D.addClass(D.div(), 'spacer-widget-above'),
1338 buttons: D.addClass(D.div(), 'spacer-widget-buttons'),
1339 below: D.addClass(D.div(), 'spacer-widget-below'),
1340
1341 up: D.addClass(
1342 D.button(zDownArrow+' Load '+nMsgContext+' more '+zDownArrow),
1343 'up'
1344 ),
1345 down: D.addClass(
1346 D.button(zUpArrow+' Load '+nMsgContext+' more '+zUpArrow),
1347 'down'
1348 ),
1349 all: D.addClass(D.button('Load More'), 'all')
1350 };
1351
1352 D.append(this.e.buttons, this.e.up, this.e.down, this.e.all);
1353 D.append(this.e.body, this.e.above, this.e.buttons, this.e.below);
1354
1355 const ms = this;
1356 this.e.up.addEventListener('click', ()=>ms.load_messages(false));
1357 this.e.down.addEventListener('click', ()=>ms.load_messages(true));
1358 this.e.all.addEventListener('click', ()=>ms.load_messages( (ms.o.iPrevId==0) ));
1359 this.set_button_visibility();
1360 };
1361
1362 cf.prototype = {
1363 set_button_visibility: function() {
1364 const o = this.o;
1365
1366 const iPrevId = (o.iPrevId!=0) ? o.iPrevId : o.iFirstInTable-1;
1367 const iNextId = (o.iNextId!=0) ? o.iNextId : o.iLastInTable+1;
1368 var nDiff = (iNextId - iPrevId) - 1;
1369
1370 D.addClass([this.e.up, this.e.down, this.e.all], 'hidden');
1371
1372 if( nDiff>0 ){
1373
1374 if( nDiff>nMsgContext && (o.iPrevId==0 || o.iNextId==0) ){
1375 nDiff = nMsgContext;
1376 }
1377
1378 if( nDiff<=nMsgContext && o.iPrevId!=0 && o.iNextId!=0 ){
1379 D.removeClass(this.e.all, 'hidden');
1380 this.e.all.innerText = (
1381 zUpArrow + " Load " + nDiff + " more " + zDownArrow
1382 );
1383 }else{
1384 if( o.iPrevId!=0 ) D.removeClass(this.e.up, 'hidden');
1385 if( o.iNextId!=0 ) D.removeClass(this.e.down, 'hidden');
1386 }
1387 }
1388 },
1389
1390 load_messages: function(bDown) {
1391 if( this.bIgnoreClick ) return;
1392
1393 var iFirst = 0; /* msgid of first message to fetch */
1394 var nFetch = 0; /* Number of messages to fetch */
1395 var iEof = 0; /* last msgid in spacers range, plus 1 */
1396
1397 const e = this.e, o = this.o;
1398 this.bIgnoreClick = true;
1399
1400 /* Figure out the required range of messages. */
1401 if( bDown ){
1402 iFirst = this.o.iNextId - nMsgContext;
1403 if( iFirst<this.o.iFirstInTable ){
1404 iFirst = this.o.iFirstInTable;
1405 }
1406 }else{
1407 iFirst = this.o.iPrevId+1;
1408 }
1409 nFetch = nMsgContext;
1410 iEof = (this.o.iNextId > 0) ? this.o.iNextId : this.o.iLastInTable+1;
1411 if( iFirst+nFetch>iEof ){
1412 nFetch = iEof - iFirst;
1413 }
1414 const ms = this;
1415 F.fetch("chat-query",{
1416 urlParams:{
1417 q: '',
1418 n: nFetch,
1419 i: iFirst
1420 },
1421 responseType: "json",
1422 onload:function(jx){
1423 const firstChildOfBelow = e.below.firstChild;
1424 jx.msgs.forEach((m) => {
1425 var mw = new Chat.MessageWidget(m);
1426 if( bDown ){
1427 e.below.insertBefore(mw.e.body, firstChildOfBelow);
1428 }else{
1429 D.append(e.above, mw.e.body);
1430 }
1431 });
1432 if( bDown ){
1433 o.iNextId -= jx.msgs.length;
1434 }else{
1435 o.iPrevId += jx.msgs.length;
1436 }
1437 ms.set_button_visibility();
1438 ms.bIgnoreClick = false;
1439 }
1440 });
1441 }
1442 };
1443
1444 return cf;
1445 })() /*SearchCtxLoader*/;
1446
1447 const BlobXferState = (function(){
1448 /* State for paste and drag/drop */
1449 const bxs = {
1450 dropDetails: document.querySelector('#chat-drop-details'),
1451 blob: undefined,
@@ -2173,28 +2323,40 @@
2323 */
2324 Chat.submitSearch = function(){
2325 const term = this.inputValue(true);
2326 const eMsgTgt = D.clearElement(
2327 this.e.viewSearch.querySelector('.message-widget-content')
2328 )
2329 if( !term ) return;
2330 D.append( eMsgTgt, "Searching for ",term," ...");
2331 F.fetch(
2332 "chat-query", {
2333 urlParams: {q: term},
2334 responseType: 'json',
2335 onload:function(jx){
2336 let previd = 0;
2337 console.log("jx =",jx);
2338 D.clearElement(eMsgTgt);
2339 jx.msgs.forEach((m)=>{
2340 const mw = new Chat.MessageWidget(m);
2341 const spacer = new Chat.SearchCtxLoader({
2342 first: jx.first,
2343 last: jx.last,
2344 previd: previd,
2345 nextid: m.msgid
2346 });
2347 D.append( eMsgTgt, spacer.e.body, mw.e.body );
2348 previd = m.msgid;
2349 });
2350 if( jx.msgs.length ){
2351 const spacer = new Chat.SearchCtxLoader({
2352 first: jx.first,
2353 last: jx.last,
2354 previd: previd,
2355 nextid: 0
2356 });
2357 D.append( eMsgTgt, spacer.e.body );
2358 }else{
2359 D.append( D.clearElement(eMsgTgt),
2360 'No results matching the search term: ',
2361 term );
2362 }
2363

Keyboard Shortcuts

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