Fossil SCM

Further improvements to the layout of merge arrows.

drh 2011-03-14 20:14 trunk
Commit a6934b4437780904bf8813ec55c81bdca55d57ab
2 files changed +8 -7 +28 -20
+8 -7
--- src/graph.c
+++ src/graph.c
@@ -45,12 +45,12 @@
4545
GraphRow *pChild; /* Child immediately above this node */
4646
u8 isDup; /* True if this is duplicate of a prior entry */
4747
u8 bDescender; /* True if riser from bottom of graph to here. */
4848
i8 iRail; /* Which rail this check-in appears on. 0-based.*/
4949
i8 mergeOut; /* Merge out to this rail. -1 if no merge-out */
50
+ u8 mergeIn[GR_MAX_RAIL]; /* Merge in from non-zero rails */
5051
int aiRiser[GR_MAX_RAIL]; /* Risers from this node to a higher row. */
51
- u32 mergeIn; /* Merge in from other rails on this bitmask */
5252
int mergeUpto; /* Draw the mergeOut rail up to this level */
5353
u32 mergeDown; /* Draw merge lines up from bottom of graph */
5454
5555
u32 railInUse; /* Mask of occupied rails at this row */
5656
};
@@ -271,27 +271,28 @@
271271
u = pParent->aiRiser[pParent->iRail];
272272
if( u>0 && u<pChild->idx ){
273273
/* The thick arrow up to the next primary child of pDesc goes
274274
** further up than the thin merge arrow riser, so draw them both
275275
** on the same rail. */
276
- pParent->mergeOut = pParent->iRail;
276
+ pParent->mergeOut = pParent->iRail*4;
277
+ if( pParent->iRail<pChild->iRail ) pParent->mergeOut += 2;
277278
pParent->mergeUpto = pChild->idx;
278279
}else{
279280
/* The thin merge arrow riser is taller than the thick primary
280281
** child riser, so use separate rails. */
281282
int iTarget = pParent->iRail;
282283
pParent->mergeOut = findFreeRail(p, pChild->idx, pParent->idx-1,
283
- 0, iTarget);
284
+ 0, iTarget)*4 + 1;
284285
pParent->mergeUpto = pChild->idx;
285
- mask = 1<<pParent->mergeOut;
286
+ mask = 1<<(pParent->mergeOut/4);
286287
for(pLoop=pChild->pNext; pLoop && pLoop->rid!=pParent->rid;
287288
pLoop=pLoop->pNext){
288289
pLoop->railInUse |= mask;
289290
}
290291
}
291292
}
292
- pChild->mergeIn |= 1<<pParent->mergeOut;
293
+ pChild->mergeIn[pParent->mergeOut/4] = (pParent->mergeOut&3)+1;
293294
}
294295
295296
296297
/*
297298
** Compute the complete graph
@@ -461,11 +462,11 @@
461462
pDesc = hashFind(p, parentRid);
462463
if( pDesc==0 ){
463464
/* Merge from a node that is off-screen */
464465
int iMrail = findFreeRail(p, pRow->idx, p->nRow, 0, 0);
465466
mask = 1<<iMrail;
466
- pRow->mergeIn |= mask;
467
+ pRow->mergeIn[iMrail] = 2;
467468
pRow->mergeDown |= mask;
468469
for(pLoop=pRow->pNext; pLoop; pLoop=pLoop->pNext){
469470
pLoop->railInUse |= mask;
470471
}
471472
}else{
@@ -491,11 +492,11 @@
491492
** Find the maximum rail number.
492493
*/
493494
p->mxRail = 0;
494495
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
495496
if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail;
496
- if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut;
497
+ if( pRow->mergeOut/4>p->mxRail ) p->mxRail = pRow->mergeOut/4;
497498
while( p->mxRail<GR_MAX_RAIL && pRow->mergeDown>((1<<(p->mxRail+1))-1) ){
498499
p->mxRail++;
499500
}
500501
}
501502
}
502503
--- src/graph.c
+++ src/graph.c
@@ -45,12 +45,12 @@
45 GraphRow *pChild; /* Child immediately above this node */
46 u8 isDup; /* True if this is duplicate of a prior entry */
47 u8 bDescender; /* True if riser from bottom of graph to here. */
48 i8 iRail; /* Which rail this check-in appears on. 0-based.*/
49 i8 mergeOut; /* Merge out to this rail. -1 if no merge-out */
 
50 int aiRiser[GR_MAX_RAIL]; /* Risers from this node to a higher row. */
51 u32 mergeIn; /* Merge in from other rails on this bitmask */
52 int mergeUpto; /* Draw the mergeOut rail up to this level */
53 u32 mergeDown; /* Draw merge lines up from bottom of graph */
54
55 u32 railInUse; /* Mask of occupied rails at this row */
56 };
@@ -271,27 +271,28 @@
271 u = pParent->aiRiser[pParent->iRail];
272 if( u>0 && u<pChild->idx ){
273 /* The thick arrow up to the next primary child of pDesc goes
274 ** further up than the thin merge arrow riser, so draw them both
275 ** on the same rail. */
276 pParent->mergeOut = pParent->iRail;
 
277 pParent->mergeUpto = pChild->idx;
278 }else{
279 /* The thin merge arrow riser is taller than the thick primary
280 ** child riser, so use separate rails. */
281 int iTarget = pParent->iRail;
282 pParent->mergeOut = findFreeRail(p, pChild->idx, pParent->idx-1,
283 0, iTarget);
284 pParent->mergeUpto = pChild->idx;
285 mask = 1<<pParent->mergeOut;
286 for(pLoop=pChild->pNext; pLoop && pLoop->rid!=pParent->rid;
287 pLoop=pLoop->pNext){
288 pLoop->railInUse |= mask;
289 }
290 }
291 }
292 pChild->mergeIn |= 1<<pParent->mergeOut;
293 }
294
295
296 /*
297 ** Compute the complete graph
@@ -461,11 +462,11 @@
461 pDesc = hashFind(p, parentRid);
462 if( pDesc==0 ){
463 /* Merge from a node that is off-screen */
464 int iMrail = findFreeRail(p, pRow->idx, p->nRow, 0, 0);
465 mask = 1<<iMrail;
466 pRow->mergeIn |= mask;
467 pRow->mergeDown |= mask;
468 for(pLoop=pRow->pNext; pLoop; pLoop=pLoop->pNext){
469 pLoop->railInUse |= mask;
470 }
471 }else{
@@ -491,11 +492,11 @@
491 ** Find the maximum rail number.
492 */
493 p->mxRail = 0;
494 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
495 if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail;
496 if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut;
497 while( p->mxRail<GR_MAX_RAIL && pRow->mergeDown>((1<<(p->mxRail+1))-1) ){
498 p->mxRail++;
499 }
500 }
501 }
502
--- src/graph.c
+++ src/graph.c
@@ -45,12 +45,12 @@
45 GraphRow *pChild; /* Child immediately above this node */
46 u8 isDup; /* True if this is duplicate of a prior entry */
47 u8 bDescender; /* True if riser from bottom of graph to here. */
48 i8 iRail; /* Which rail this check-in appears on. 0-based.*/
49 i8 mergeOut; /* Merge out to this rail. -1 if no merge-out */
50 u8 mergeIn[GR_MAX_RAIL]; /* Merge in from non-zero rails */
51 int aiRiser[GR_MAX_RAIL]; /* Risers from this node to a higher row. */
 
52 int mergeUpto; /* Draw the mergeOut rail up to this level */
53 u32 mergeDown; /* Draw merge lines up from bottom of graph */
54
55 u32 railInUse; /* Mask of occupied rails at this row */
56 };
@@ -271,27 +271,28 @@
271 u = pParent->aiRiser[pParent->iRail];
272 if( u>0 && u<pChild->idx ){
273 /* The thick arrow up to the next primary child of pDesc goes
274 ** further up than the thin merge arrow riser, so draw them both
275 ** on the same rail. */
276 pParent->mergeOut = pParent->iRail*4;
277 if( pParent->iRail<pChild->iRail ) pParent->mergeOut += 2;
278 pParent->mergeUpto = pChild->idx;
279 }else{
280 /* The thin merge arrow riser is taller than the thick primary
281 ** child riser, so use separate rails. */
282 int iTarget = pParent->iRail;
283 pParent->mergeOut = findFreeRail(p, pChild->idx, pParent->idx-1,
284 0, iTarget)*4 + 1;
285 pParent->mergeUpto = pChild->idx;
286 mask = 1<<(pParent->mergeOut/4);
287 for(pLoop=pChild->pNext; pLoop && pLoop->rid!=pParent->rid;
288 pLoop=pLoop->pNext){
289 pLoop->railInUse |= mask;
290 }
291 }
292 }
293 pChild->mergeIn[pParent->mergeOut/4] = (pParent->mergeOut&3)+1;
294 }
295
296
297 /*
298 ** Compute the complete graph
@@ -461,11 +462,11 @@
462 pDesc = hashFind(p, parentRid);
463 if( pDesc==0 ){
464 /* Merge from a node that is off-screen */
465 int iMrail = findFreeRail(p, pRow->idx, p->nRow, 0, 0);
466 mask = 1<<iMrail;
467 pRow->mergeIn[iMrail] = 2;
468 pRow->mergeDown |= mask;
469 for(pLoop=pRow->pNext; pLoop; pLoop=pLoop->pNext){
470 pLoop->railInUse |= mask;
471 }
472 }else{
@@ -491,11 +492,11 @@
492 ** Find the maximum rail number.
493 */
494 p->mxRail = 0;
495 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
496 if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail;
497 if( pRow->mergeOut/4>p->mxRail ) p->mxRail = pRow->mergeOut/4;
498 while( p->mxRail<GR_MAX_RAIL && pRow->mergeDown>((1<<(p->mxRail+1))-1) ){
499 p->mxRail++;
500 }
501 }
502 }
503
+28 -20
--- src/timeline.c
+++ src/timeline.c
@@ -388,40 +388,43 @@
388388
** bg: The background color for this row
389389
** r: The "rail" that the node for this row sits on. The left-most
390390
** rail is 0 and the number increases to the right.
391391
** d: True if there is a "descender" - an arrow coming from the bottom
392392
** of the page straight up to this node.
393
- ** mo: "merge-out". If non-zero, this is one more than the rail on which
394
- ** a merge arrow travels upward. The merge arrow is drawn upwards
393
+ ** mo: "merge-out". If non-zero, this is one more than the x-coordinate
394
+ ** for the upward portion of a merge arrow. The merge arrow goes up
395395
** to the row identified by mu:. If this value is zero then
396396
** node has no merge children and no merge-out line is drawn.
397397
** mu: The id of the row which is the top of the merge-out arrow.
398
- ** md: A bitmask of rails on which merge-arrow descenders should be
399
- ** drawn from this row to the bottom of the page. The least
400
- ** significant bit (1) corresponds to rail 0. The 2-bit corresponds
401
- ** to rail 1. And so forth. This value is 0 if there are no
402
- ** merge-arrow descenders.
403398
** u: Draw a thick child-line out of the top of this node and up to
404399
** the node with an id equal to this value. 0 if there is no
405400
** thick-line riser.
406401
** au: An array of integers that define thick-line risers for branches.
407402
** The integers are in pairs. For each pair, the first integer is
408403
** is the rail on which the riser should run and the second integer
409404
** is the id of the node upto which the riser should run.
410
- ** mi: "merge-in". An array of integer rail numbers from which
411
- ** merge arrows should be drawn into this node.
405
+ ** mi: "merge-in". An array of integer x-coordinates from which
406
+ ** merge arrows should be drawn into this node. If the value is
407
+ ** negative, then the x-coordinate is the absolute value of mi[]
408
+ ** and a thin merge-arrow descender is drawn to the bottom of
409
+ ** the screen.
412410
*/
413411
cgi_printf("var rowinfo = [\n");
414412
for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
415
- cgi_printf("{id:%d,bg:\"%s\",r:%d,d:%d,mo:%d,mu:%d,md:%u,u:%d,au:",
413
+ int mo = pRow->mergeOut;
414
+ if( mo<0 ){
415
+ mo = 0;
416
+ }else{
417
+ mo = (mo/4)*20 - 3 + 4*(mo&3);
418
+ }
419
+ cgi_printf("{id:%d,bg:\"%s\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:",
416420
pRow->idx, /* id */
417421
pRow->zBgClr, /* bg */
418422
pRow->iRail, /* r */
419423
pRow->bDescender, /* d */
420
- pRow->mergeOut+1, /* mo */
424
+ mo, /* mo */
421425
pRow->mergeUpto, /* mu */
422
- pRow->mergeDown, /* md */
423426
pRow->aiRiser[pRow->iRail] /* u */
424427
);
425428
/* u */
426429
cSep = '[';
427430
for(i=0; i<GR_MAX_RAIL; i++){
@@ -434,12 +437,14 @@
434437
if( cSep=='[' ) cgi_printf("[");
435438
cgi_printf("],mi:");
436439
/* mi */
437440
cSep = '[';
438441
for(i=0; i<GR_MAX_RAIL; i++){
439
- if( pRow->mergeIn & (1<<i) ){
440
- cgi_printf("%c%d", cSep, i);
442
+ if( pRow->mergeIn[i] ){
443
+ int mi = i*20 - 8 + 4*pRow->mergeIn[i];
444
+ if( pRow->mergeDown & (1<<i) ) mi = -mi;
445
+ cgi_printf("%c%d", cSep, mi);
441446
cSep = ',';
442447
}
443448
}
444449
if( cSep=='[' ) cgi_printf("[");
445450
cgi_printf("]}%s", pRow->pNext ? ",\n" : "];\n");
@@ -518,16 +523,16 @@
518523
@ }
519524
@ if( p.d ){
520525
@ drawUpArrow(p.x, p.y+6, btm);
521526
@ }
522527
@ if( p.mo>0 ){
523
- @ var x1 = (p.mo-1)*20 + left + 4;
528
+ @ var x1 = p.mo + left - 1;
524529
@ var y1 = p.y-3;
525530
@ var x0 = x1>p.x ? p.x+7 : p.x-6;
526531
@ var u = rowinfo[p.mu-1];
527532
@ var y0 = u.y+5;
528
- @ if( x1==p.x+4 ){
533
+ @ if( x1>=p.x-5 && x1<=p.x+5 ){
529534
@ y1 = p.y-5;
530535
@ }else{
531536
@ drawThinLine(x0,y1,x1,y1);
532537
@ }
533538
@ drawThinLine(x1,y0,x1,y1);
@@ -548,19 +553,22 @@
548553
@ drawBox("#600000",u.x-11,u.y-2,u.x-10,u.y+3);
549554
@ }
550555
@ }
551556
@ for(var j in p.mi){
552557
@ var y0 = p.y+5;
553
- @ var mx = p.mi[j]*20 + left + 4;
558
+ @ var mx = p.mi[j];
559
+ @ if( mx<0 ){
560
+ @ mx = left-mx;
561
+ @ drawThinLine(mx,y0,mx,btm);
562
+ @ }else{
563
+ @ mx += left;
564
+ @ }
554565
@ if( mx>p.x ){
555566
@ drawThinArrow(y0,mx,p.x+6);
556567
@ }else{
557568
@ drawThinArrow(y0,mx,p.x-5);
558569
@ }
559
- @ if( (1<<p.mi[j])&p.md ){
560
- @ drawThinLine(mx,y0,mx,btm);
561
- @ }
562570
@ }
563571
@ }
564572
@ function renderGraph(){
565573
@ var canvasDiv = document.getElementById("canvas");
566574
@ while( canvasDiv.hasChildNodes() ){
567575
--- src/timeline.c
+++ src/timeline.c
@@ -388,40 +388,43 @@
388 ** bg: The background color for this row
389 ** r: The "rail" that the node for this row sits on. The left-most
390 ** rail is 0 and the number increases to the right.
391 ** d: True if there is a "descender" - an arrow coming from the bottom
392 ** of the page straight up to this node.
393 ** mo: "merge-out". If non-zero, this is one more than the rail on which
394 ** a merge arrow travels upward. The merge arrow is drawn upwards
395 ** to the row identified by mu:. If this value is zero then
396 ** node has no merge children and no merge-out line is drawn.
397 ** mu: The id of the row which is the top of the merge-out arrow.
398 ** md: A bitmask of rails on which merge-arrow descenders should be
399 ** drawn from this row to the bottom of the page. The least
400 ** significant bit (1) corresponds to rail 0. The 2-bit corresponds
401 ** to rail 1. And so forth. This value is 0 if there are no
402 ** merge-arrow descenders.
403 ** u: Draw a thick child-line out of the top of this node and up to
404 ** the node with an id equal to this value. 0 if there is no
405 ** thick-line riser.
406 ** au: An array of integers that define thick-line risers for branches.
407 ** The integers are in pairs. For each pair, the first integer is
408 ** is the rail on which the riser should run and the second integer
409 ** is the id of the node upto which the riser should run.
410 ** mi: "merge-in". An array of integer rail numbers from which
411 ** merge arrows should be drawn into this node.
 
 
 
412 */
413 cgi_printf("var rowinfo = [\n");
414 for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
415 cgi_printf("{id:%d,bg:\"%s\",r:%d,d:%d,mo:%d,mu:%d,md:%u,u:%d,au:",
 
 
 
 
 
 
416 pRow->idx, /* id */
417 pRow->zBgClr, /* bg */
418 pRow->iRail, /* r */
419 pRow->bDescender, /* d */
420 pRow->mergeOut+1, /* mo */
421 pRow->mergeUpto, /* mu */
422 pRow->mergeDown, /* md */
423 pRow->aiRiser[pRow->iRail] /* u */
424 );
425 /* u */
426 cSep = '[';
427 for(i=0; i<GR_MAX_RAIL; i++){
@@ -434,12 +437,14 @@
434 if( cSep=='[' ) cgi_printf("[");
435 cgi_printf("],mi:");
436 /* mi */
437 cSep = '[';
438 for(i=0; i<GR_MAX_RAIL; i++){
439 if( pRow->mergeIn & (1<<i) ){
440 cgi_printf("%c%d", cSep, i);
 
 
441 cSep = ',';
442 }
443 }
444 if( cSep=='[' ) cgi_printf("[");
445 cgi_printf("]}%s", pRow->pNext ? ",\n" : "];\n");
@@ -518,16 +523,16 @@
518 @ }
519 @ if( p.d ){
520 @ drawUpArrow(p.x, p.y+6, btm);
521 @ }
522 @ if( p.mo>0 ){
523 @ var x1 = (p.mo-1)*20 + left + 4;
524 @ var y1 = p.y-3;
525 @ var x0 = x1>p.x ? p.x+7 : p.x-6;
526 @ var u = rowinfo[p.mu-1];
527 @ var y0 = u.y+5;
528 @ if( x1==p.x+4 ){
529 @ y1 = p.y-5;
530 @ }else{
531 @ drawThinLine(x0,y1,x1,y1);
532 @ }
533 @ drawThinLine(x1,y0,x1,y1);
@@ -548,19 +553,22 @@
548 @ drawBox("#600000",u.x-11,u.y-2,u.x-10,u.y+3);
549 @ }
550 @ }
551 @ for(var j in p.mi){
552 @ var y0 = p.y+5;
553 @ var mx = p.mi[j]*20 + left + 4;
 
 
 
 
 
 
554 @ if( mx>p.x ){
555 @ drawThinArrow(y0,mx,p.x+6);
556 @ }else{
557 @ drawThinArrow(y0,mx,p.x-5);
558 @ }
559 @ if( (1<<p.mi[j])&p.md ){
560 @ drawThinLine(mx,y0,mx,btm);
561 @ }
562 @ }
563 @ }
564 @ function renderGraph(){
565 @ var canvasDiv = document.getElementById("canvas");
566 @ while( canvasDiv.hasChildNodes() ){
567
--- src/timeline.c
+++ src/timeline.c
@@ -388,40 +388,43 @@
388 ** bg: The background color for this row
389 ** r: The "rail" that the node for this row sits on. The left-most
390 ** rail is 0 and the number increases to the right.
391 ** d: True if there is a "descender" - an arrow coming from the bottom
392 ** of the page straight up to this node.
393 ** mo: "merge-out". If non-zero, this is one more than the x-coordinate
394 ** for the upward portion of a merge arrow. The merge arrow goes up
395 ** to the row identified by mu:. If this value is zero then
396 ** node has no merge children and no merge-out line is drawn.
397 ** mu: The id of the row which is the top of the merge-out arrow.
 
 
 
 
 
398 ** u: Draw a thick child-line out of the top of this node and up to
399 ** the node with an id equal to this value. 0 if there is no
400 ** thick-line riser.
401 ** au: An array of integers that define thick-line risers for branches.
402 ** The integers are in pairs. For each pair, the first integer is
403 ** is the rail on which the riser should run and the second integer
404 ** is the id of the node upto which the riser should run.
405 ** mi: "merge-in". An array of integer x-coordinates from which
406 ** merge arrows should be drawn into this node. If the value is
407 ** negative, then the x-coordinate is the absolute value of mi[]
408 ** and a thin merge-arrow descender is drawn to the bottom of
409 ** the screen.
410 */
411 cgi_printf("var rowinfo = [\n");
412 for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
413 int mo = pRow->mergeOut;
414 if( mo<0 ){
415 mo = 0;
416 }else{
417 mo = (mo/4)*20 - 3 + 4*(mo&3);
418 }
419 cgi_printf("{id:%d,bg:\"%s\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:",
420 pRow->idx, /* id */
421 pRow->zBgClr, /* bg */
422 pRow->iRail, /* r */
423 pRow->bDescender, /* d */
424 mo, /* mo */
425 pRow->mergeUpto, /* mu */
 
426 pRow->aiRiser[pRow->iRail] /* u */
427 );
428 /* u */
429 cSep = '[';
430 for(i=0; i<GR_MAX_RAIL; i++){
@@ -434,12 +437,14 @@
437 if( cSep=='[' ) cgi_printf("[");
438 cgi_printf("],mi:");
439 /* mi */
440 cSep = '[';
441 for(i=0; i<GR_MAX_RAIL; i++){
442 if( pRow->mergeIn[i] ){
443 int mi = i*20 - 8 + 4*pRow->mergeIn[i];
444 if( pRow->mergeDown & (1<<i) ) mi = -mi;
445 cgi_printf("%c%d", cSep, mi);
446 cSep = ',';
447 }
448 }
449 if( cSep=='[' ) cgi_printf("[");
450 cgi_printf("]}%s", pRow->pNext ? ",\n" : "];\n");
@@ -518,16 +523,16 @@
523 @ }
524 @ if( p.d ){
525 @ drawUpArrow(p.x, p.y+6, btm);
526 @ }
527 @ if( p.mo>0 ){
528 @ var x1 = p.mo + left - 1;
529 @ var y1 = p.y-3;
530 @ var x0 = x1>p.x ? p.x+7 : p.x-6;
531 @ var u = rowinfo[p.mu-1];
532 @ var y0 = u.y+5;
533 @ if( x1>=p.x-5 && x1<=p.x+5 ){
534 @ y1 = p.y-5;
535 @ }else{
536 @ drawThinLine(x0,y1,x1,y1);
537 @ }
538 @ drawThinLine(x1,y0,x1,y1);
@@ -548,19 +553,22 @@
553 @ drawBox("#600000",u.x-11,u.y-2,u.x-10,u.y+3);
554 @ }
555 @ }
556 @ for(var j in p.mi){
557 @ var y0 = p.y+5;
558 @ var mx = p.mi[j];
559 @ if( mx<0 ){
560 @ mx = left-mx;
561 @ drawThinLine(mx,y0,mx,btm);
562 @ }else{
563 @ mx += left;
564 @ }
565 @ if( mx>p.x ){
566 @ drawThinArrow(y0,mx,p.x+6);
567 @ }else{
568 @ drawThinArrow(y0,mx,p.x-5);
569 @ }
 
 
 
570 @ }
571 @ }
572 @ function renderGraph(){
573 @ var canvasDiv = document.getElementById("canvas");
574 @ while( canvasDiv.hasChildNodes() ){
575

Keyboard Shortcuts

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