Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Fix timeline rendering so that multiple timelines can be drawn on each page without interferring with one another. Move the "Referenced By" section of the /info page upward and rename it as "References". Added the /test-backlink page for showing all pages that contain references. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
ba3444327f36442c00d01e1873be1204 |
| User & Date: | drh 2017-07-05 12:33:33.919 |
Context
|
2017-07-05
| ||
| 13:00 | In the timeline graph rendering code, hard-code the topRow value in the generated javascript. check-in: e76f3bbe45 user: drh tags: trunk | |
| 12:33 | Fix timeline rendering so that multiple timelines can be drawn on each page without interferring with one another. Move the "Referenced By" section of the /info page upward and rename it as "References". Added the /test-backlink page for showing all pages that contain references. check-in: ba3444327f user: drh tags: trunk | |
| 11:12 | Add the "Referenced By" section to the /info page. check-in: fae745cf0f user: drh tags: trunk | |
Changes
Changes to src/finfo.c.
| ︙ | ︙ | |||
308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
HQuery url;
GraphContext *pGraph;
int brBg = P("brbg")!=0;
int uBg = P("ubg")!=0;
int fDebug = atoi(PD("debug","0"));
int fShowId = P("showid")!=0;
Stmt qparent;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
style_header("File History");
login_anonymous_available();
url_initialize(&url, "finfo");
if( brBg ) url_add_parameter(&url, "brbg", 0);
| > | 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
HQuery url;
GraphContext *pGraph;
int brBg = P("brbg")!=0;
int uBg = P("ubg")!=0;
int fDebug = atoi(PD("debug","0"));
int fShowId = P("showid")!=0;
Stmt qparent;
int iTableId = timeline_tableid();
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
style_header("File History");
login_anonymous_available();
url_initialize(&url, "finfo");
if( brBg ) url_add_parameter(&url, "brbg", 0);
|
| ︙ | ︙ | |||
413 414 415 416 417 418 419 |
blob_appendf(&title, "History of ");
hyperlinked_path(zFilename, &title, 0, "tree", "");
if( fShowId ) blob_appendf(&title, " (%d)", fnid);
}
@ <h2>%b(&title)</h2>
blob_reset(&title);
pGraph = graph_init();
| | | 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
blob_appendf(&title, "History of ");
hyperlinked_path(zFilename, &title, 0, "tree", "");
if( fShowId ) blob_appendf(&title, " (%d)", fnid);
}
@ <h2>%b(&title)</h2>
blob_reset(&title);
pGraph = graph_init();
@ <table id="timelineTable%d(iTableId)" class="timelineTable">
if( baseCheckin ){
db_prepare(&qparent,
"SELECT DISTINCT pid FROM mlink"
" WHERE fid=:fid AND mid=:mid AND pid>0 AND fnid=:fnid"
" AND pmid IN (SELECT rid FROM ancestor)"
" ORDER BY isaux /*sort*/"
);
|
| ︙ | ︙ | |||
556 557 558 559 560 561 562 |
graph_free(pGraph);
pGraph = 0;
}else{
@ <tr class="timelineBottom"><td></td><td></td><td></td></tr>
}
}
@ </table>
| | | 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 |
graph_free(pGraph);
pGraph = 0;
}else{
@ <tr class="timelineBottom"><td></td><td></td><td></td></tr>
}
}
@ </table>
timeline_output_graph_javascript(pGraph, 0, iTableId, 1);
style_footer();
}
/*
** WEBPAGE: mlink
** URL: /mlink?name=FILENAME
** URL: /mlink?ci=NAME
|
| ︙ | ︙ |
Changes to src/graph.c.
| ︙ | ︙ | |||
182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
const char *zBranch, /* Branch for this check-in */
const char *zBgClr, /* Background color. NULL or "" for white. */
const char *zUuid, /* hash name of the object being graphed */
int isLeaf /* True if this row is a leaf */
){
GraphRow *pRow;
int nByte;
if( p->nErr ) return 0;
nByte = sizeof(GraphRow);
nByte += sizeof(pRow->aParent[0])*nParent;
pRow = (GraphRow*)safeMalloc( nByte );
pRow->aParent = (int*)&pRow[1];
pRow->rid = rid;
| > | 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
const char *zBranch, /* Branch for this check-in */
const char *zBgClr, /* Background color. NULL or "" for white. */
const char *zUuid, /* hash name of the object being graphed */
int isLeaf /* True if this row is a leaf */
){
GraphRow *pRow;
int nByte;
static int nRow = 0;
if( p->nErr ) return 0;
nByte = sizeof(GraphRow);
nByte += sizeof(pRow->aParent[0])*nParent;
pRow = (GraphRow*)safeMalloc( nByte );
pRow->aParent = (int*)&pRow[1];
pRow->rid = rid;
|
| ︙ | ︙ | |||
205 206 207 208 209 210 211 |
if( p->pFirst==0 ){
p->pFirst = pRow;
}else{
p->pLast->pNext = pRow;
}
p->pLast = pRow;
p->nRow++;
| | | 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
if( p->pFirst==0 ){
p->pFirst = pRow;
}else{
p->pLast->pNext = pRow;
}
p->pLast = pRow;
p->nRow++;
pRow->idx = pRow->idxTop = ++nRow;
return pRow->idx;
}
/*
** Return the index of a rail currently not in use for any row between
** top and bottom, inclusive.
*/
|
| ︙ | ︙ |
Changes to src/info.c.
| ︙ | ︙ | |||
332 333 334 335 336 337 338 339 | db_prepare(&q, "%s", blob_sql_text(&sql)); www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH, 0, 0, rid, 0); db_finalize(&q); } /* ** Show a graph all wiki, tickets, and check-ins that refer to object zUuid. */ | > > > | > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > | 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 |
db_prepare(&q, "%s", blob_sql_text(&sql));
www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH, 0, 0, rid, 0);
db_finalize(&q);
}
/*
** Show a graph all wiki, tickets, and check-ins that refer to object zUuid.
**
** If zLabel is not NULL and the graph is not empty, then output zLabel as
** a prefix to the graph.
*/
void render_backlink_graph(const char *zUuid, const char *zLabel){
Blob sql;
Stmt q;
char *zGlob;
zGlob = mprintf("%.5s*", zUuid);
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
"DELETE FROM ok;"
"INSERT OR IGNORE INTO ok"
" SELECT srcid FROM backlink"
" WHERE target GLOB %Q"
" AND %Q GLOB (target || '*');",
zGlob, zUuid
);
if( !db_exists("SELECT 1 FROM ok") ) return;
if( zLabel ) cgi_printf("%s", zLabel);
blob_zero(&sql);
blob_append(&sql, timeline_query_for_www(), -1);
blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
db_prepare(&q, "%s", blob_sql_text(&sql));
www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH, 0, 0, 0, 0);
db_finalize(&q);
}
/*
** WEBPAGE: test-backlinks
**
** Show a timeline of all check-ins and other events that have entries
** in the backlink table. This is used for testing the rendering
** of the "References" section of the /info page.
*/
void backlink_timeline_page(void){
Blob sql;
Stmt q;
login_check_credentials();
if( !g.perm.Read || !g.perm.RdTkt || !g.perm.RdWiki ){
login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
return;
}
style_header("Backlink Timeline (Internal Testing Use)");
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
"DELETE FROM ok;"
"INSERT OR IGNORE INTO ok"
" SELECT blob.rid FROM backlink, blob"
" WHERE blob.uuid BETWEEN backlink.target AND (backlink.target||'x')"
);
blob_zero(&sql);
blob_append(&sql, timeline_query_for_www(), -1);
blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
db_prepare(&q, "%s", blob_sql_text(&sql));
www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH, 0, 0, 0, 0);
db_finalize(&q);
style_footer();
}
/*
** Append the difference between artifacts to the output
*/
static void append_diff(
|
| ︙ | ︙ | |||
733 734 735 736 737 738 739 740 741 742 |
}
@ </table>
}else{
style_header("Check-in Information");
login_anonymous_available();
}
db_finalize(&q1);
showTags(rid);
@ <div class="section">Context</div>
render_checkin_context(rid, 0);
| > < | 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 |
}
@ </table>
}else{
style_header("Check-in Information");
login_anonymous_available();
}
db_finalize(&q1);
render_backlink_graph(zUuid, "<div class=\"section\">References</div>\n");
showTags(rid);
@ <div class="section">Context</div>
render_checkin_context(rid, 0);
@ <div class="section">Changes</div>
@ <div class="sectionmenu">
verboseFlag = g.zPath[0]!='c';
if( db_get_boolean("show-version-diffs", 0)==0 ){
verboseFlag = !verboseFlag;
zPage = "ci";
zPageHide = "vinfo";
|
| ︙ | ︙ |
Changes to src/timeline.c.
| ︙ | ︙ | |||
199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
zBr = P(zNm);
@ <input type="text" size="30" name='%s(zNm)' value='%h(PD(zNm,""))'><br />
}
@ <input type="submit">
@ </form>
style_footer();
}
/*
** Output a timeline in the web format given a query. The query
** should return these columns:
**
** 0. rid
** 1. UUID
| > > > > > > > > | 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
zBr = P(zNm);
@ <input type="text" size="30" name='%s(zNm)' value='%h(PD(zNm,""))'><br />
}
@ <input type="submit">
@ </form>
style_footer();
}
/*
** Return a new timelineTable id.
*/
int timeline_tableid(void){
static int id = 0;
return id++;
}
/*
** Output a timeline in the web format given a query. The query
** should return these columns:
**
** 0. rid
** 1. UUID
|
| ︙ | ︙ | |||
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
Stmt fchngQuery; /* Query for file changes on check-ins */
static Stmt qbranch;
int pendingEndTr = 0; /* True if a </td></tr> is needed */
int vid = 0; /* Current checkout version */
int dateFormat = 0; /* 0: HH:MM (default) */
int bCommentGitStyle = 0; /* Only show comments through first blank line */
const char *zDateFmt;
if( fossil_strcmp(g.zIpAddr, "127.0.0.1")==0 && db_open_local(0) ){
vid = db_lget_int("checkout", 0);
}
zPrevDate[0] = 0;
mxWikiLen = db_get_int("timeline-max-comment", 0);
dateFormat = db_get_int("timeline-date-format", 0);
bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0);
zDateFmt = P("datefmt");
if( zDateFmt ) dateFormat = atoi(zDateFmt);
if( tmFlags & TIMELINE_GRAPH ){
pGraph = graph_init();
}
db_static_prepare(&qbranch,
"SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
TAG_BRANCH
);
| > | | 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
Stmt fchngQuery; /* Query for file changes on check-ins */
static Stmt qbranch;
int pendingEndTr = 0; /* True if a </td></tr> is needed */
int vid = 0; /* Current checkout version */
int dateFormat = 0; /* 0: HH:MM (default) */
int bCommentGitStyle = 0; /* Only show comments through first blank line */
const char *zDateFmt;
int iTableId = timeline_tableid();
if( fossil_strcmp(g.zIpAddr, "127.0.0.1")==0 && db_open_local(0) ){
vid = db_lget_int("checkout", 0);
}
zPrevDate[0] = 0;
mxWikiLen = db_get_int("timeline-max-comment", 0);
dateFormat = db_get_int("timeline-date-format", 0);
bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0);
zDateFmt = P("datefmt");
if( zDateFmt ) dateFormat = atoi(zDateFmt);
if( tmFlags & TIMELINE_GRAPH ){
pGraph = graph_init();
}
db_static_prepare(&qbranch,
"SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
TAG_BRANCH
);
@ <table id="timelineTable%d(iTableId)" class="timelineTable">
blob_zero(&comment);
while( db_step(pQuery)==SQLITE_ROW ){
int rid = db_column_int(pQuery, 0);
const char *zUuid = db_column_text(pQuery, 1);
int isLeaf = db_column_int(pQuery, 5);
const char *zBgClr = db_column_text(pQuery, 6);
const char *zDate = db_column_text(pQuery, 2);
|
| ︙ | ︙ | |||
621 622 623 624 625 626 627 |
pGraph = 0;
}else{
@ <tr class="timelineBottom"><td></td><td></td><td></td></tr>
}
}
@ </table>
if( fchngQueryInit ) db_finalize(&fchngQuery);
| | | 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 |
pGraph = 0;
}else{
@ <tr class="timelineBottom"><td></td><td></td><td></td></tr>
}
}
@ </table>
if( fchngQueryInit ) db_finalize(&fchngQuery);
timeline_output_graph_javascript(pGraph, (tmFlags & TIMELINE_DISJOINT)!=0, iTableId, 0);
}
/*
** Change the RGB background color given in the argument in a foreground
** color with the same hue.
*/
static const char *bg_to_fg(const char *zIn){
|
| ︙ | ︙ | |||
662 663 664 665 666 667 668 669 670 671 672 673 674 675 |
/*
** Generate all of the necessary javascript to generate a timeline
** graph.
*/
void timeline_output_graph_javascript(
GraphContext *pGraph, /* The graph to be displayed */
int omitDescenders, /* True to omit descenders */
int fileDiff /* True for file diff. False for check-in diff */
){
if( pGraph && pGraph->nErr==0 && pGraph->nRow>0 ){
GraphRow *pRow;
int i;
char cSep;
int iRailPitch; /* Pixels between consecutive rails */
| > | 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 |
/*
** Generate all of the necessary javascript to generate a timeline
** graph.
*/
void timeline_output_graph_javascript(
GraphContext *pGraph, /* The graph to be displayed */
int omitDescenders, /* True to omit descenders */
int iTableId, /* Identifier for the timelineTable */
int fileDiff /* True for file diff. False for check-in diff */
){
if( pGraph && pGraph->nErr==0 && pGraph->nRow>0 ){
GraphRow *pRow;
int i;
char cSep;
int iRailPitch; /* Pixels between consecutive rails */
|
| ︙ | ︙ | |||
697 698 699 700 701 702 703 |
@ document.querySelector("head").appendChild(style);
@ }
/* the rowinfo[] array contains all the information needed to generate
** the graph. Each entry contains information for a single row:
**
** id: The id of the <div> element for the row. This is an integer.
** to get an actual id, prepend "m" to the integer. The top node
| | | 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 |
@ document.querySelector("head").appendChild(style);
@ }
/* the rowinfo[] array contains all the information needed to generate
** the graph. Each entry contains information for a single row:
**
** id: The id of the <div> element for the row. This is an integer.
** to get an actual id, prepend "m" to the integer. The top node
** is topRow and numbers increase moving down the timeline.
** bg: The background color for this row
** r: The "rail" that the node for this row sits on. The left-most
** rail is 0 and the number increases to the right.
** d: True if there is a "descender" - an arrow coming from the bottom
** of the page straight up to this node.
** mo: "merge-out". If non-negative, this is the rail position
** for the upward portion of a merge arrow. The merge arrow goes up
|
| ︙ | ︙ | |||
723 724 725 726 727 728 729 730 731 732 733 734 735 736 |
** mi: "merge-in". An array of integer rail positions from which
** merge arrows should be drawn into this node. If the value is
** negative, then the rail position is the absolute value of mi[]
** and a thin merge-arrow descender is drawn to the bottom of
** the screen.
** h: The artifact hash of the object being graphed
*/
cgi_printf("var rowinfo = [\n");
for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
cgi_printf("{id:%d,bg:\"%s\",r:%d,d:%d,mo:%d,mu:%d,u:%d,f:%d,au:",
pRow->idx, /* id */
pRow->zBgClr, /* bg */
pRow->iRail, /* r */
pRow->bDescender, /* d */
| > | 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 |
** mi: "merge-in". An array of integer rail positions from which
** merge arrows should be drawn into this node. If the value is
** negative, then the rail position is the absolute value of mi[]
** and a thin merge-arrow descender is drawn to the bottom of
** the screen.
** h: The artifact hash of the object being graphed
*/
if( pGraph->pFirst ) cgi_printf("var topRow = %d\n", pGraph->pFirst->idx);
cgi_printf("var rowinfo = [\n");
for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
cgi_printf("{id:%d,bg:\"%s\",r:%d,d:%d,mo:%d,mu:%d,u:%d,f:%d,au:",
pRow->idx, /* id */
pRow->zBgClr, /* bg */
pRow->iRail, /* r */
pRow->bDescender, /* d */
|
| ︙ | ︙ | |||
770 771 772 773 774 775 776 |
cgi_printf("var nrail = %d\n", pGraph->mxRail+1);
graph_free(pGraph);
@ var canvasDiv;
@ var railPitch;
@ var mergeOffset;
@ var node, arrow, arrowSmall, line, mArrow, mLine, wArrow, wLine;
@ function initGraph(){
| | | 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 |
cgi_printf("var nrail = %d\n", pGraph->mxRail+1);
graph_free(pGraph);
@ var canvasDiv;
@ var railPitch;
@ var mergeOffset;
@ var node, arrow, arrowSmall, line, mArrow, mLine, wArrow, wLine;
@ function initGraph(){
@ var parent = gebi("timelineTable%d(iTableId)").rows[0].cells[1];
@ parent.style.verticalAlign = "top";
@ canvasDiv = document.createElement("div");
@ canvasDiv.className = "tl-canvas";
@ canvasDiv.style.position = "absolute";
@ parent.appendChild(canvasDiv);
@
@ var elems = {};
|
| ︙ | ︙ | |||
899 900 901 902 903 904 905 |
@ var y = miLineY(p);
@ drawMergeLine(x0,y,x1,null);
@ var x = p.x + (p.r<rail ? node.w : -mArrow.w);
@ var cls = "arrow merge " + (p.r<rail ? "l" : "r");
@ drawBox(cls,null,x,y+(mLine.w-mArrow.h)/2);
@ }
@ function drawNode(p, btm){
| | | | 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 |
@ var y = miLineY(p);
@ drawMergeLine(x0,y,x1,null);
@ var x = p.x + (p.r<rail ? node.w : -mArrow.w);
@ var cls = "arrow merge " + (p.r<rail ? "l" : "r");
@ drawBox(cls,null,x,y+(mLine.w-mArrow.h)/2);
@ }
@ function drawNode(p, btm){
@ if( p.u>0 ) drawUpArrow(p,rowinfo[p.u-topRow],p.fg);
@ var cls = node.cls;
@ if( p.mi.length ) cls += " merge";
@ if( p.f&1 ) cls += " leaf";
@ var n = drawBox(cls,p.bg,p.x,p.y);
@ n.id = "tln"+p.id;
@ n.onclick = clickOnNode;
@ n.style.zIndex = 10;
if( !omitDescenders ){
@ if( p.u==0 ) drawUpArrow(p,{x: p.x, y: -node.h},p.fg);
@ if( p.d ) drawUpArrow({x: p.x, y: btm-node.h/2},p,p.fg);
}
@ if( p.mo>=0 ){
@ var x0 = p.x + node.w/2;
@ var x1 = p.mo*railPitch + node.w/2;
@ var u = rowinfo[p.mu-topRow];
@ var y1 = miLineY(u);
@ if( p.u<0 || p.mo!=p.r ){
@ x1 += mergeLines[p.mo] = -mLine.w/2;
@ var y0 = p.y+2;
@ if( p.r!=p.mo ) drawMergeLine(x0,y0,x1+(x0<x1 ? mLine.w : 0),null);
@ drawMergeLine(x1,y0+mLine.w,null,y1);
@ }else if( mergeOffset ){
|
| ︙ | ︙ | |||
938 939 940 941 942 943 944 |
@ var x0 = p.x + node.w/2;
@ var x1 = rail*railPitch + (node.w-line.w)/2;
@ if( x0<x1 ){
@ x0 = Math.ceil(x0);
@ x1 += line.w;
@ }
@ var y0 = p.y + (node.h-line.w)/2;
| | | 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 |
@ var x0 = p.x + node.w/2;
@ var x1 = rail*railPitch + (node.w-line.w)/2;
@ if( x0<x1 ){
@ x0 = Math.ceil(x0);
@ x1 += line.w;
@ }
@ var y0 = p.y + (node.h-line.w)/2;
@ var u = rowinfo[p.au[i+1]-topRow];
@ if( u.id<p.id ){
@ drawLine(line,u.fg,x0,y0,x1,null);
@ drawUpArrow(p,u,u.fg);
@ }else{
@ var y1 = u.y + (node.h-line.w)/2;
@ drawLine(wLine,u.fg,x0,y0,x1,null);
@ drawLine(wLine,u.fg,x1-line.w,y0,null,y1+line.w);
|
| ︙ | ︙ | |||
984 985 986 987 988 989 990 |
@ var btm = absoluteY(tlBtm) - canvasY + tlBtm.offsetHeight;
@ for( var i=rowinfo.length-1; i>=0; i-- ){
@ drawNode(rowinfo[i], btm);
@ }
@ }
@ var selRow;
@ function clickOnNode(){
| | | 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 |
@ var btm = absoluteY(tlBtm) - canvasY + tlBtm.offsetHeight;
@ for( var i=rowinfo.length-1; i>=0; i-- ){
@ drawNode(rowinfo[i], btm);
@ }
@ }
@ var selRow;
@ function clickOnNode(){
@ var p = rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-topRow];
@ if( !selRow ){
@ selRow = p;
@ this.className += " sel";
@ canvasDiv.className += " sel";
@ }else if( selRow==p ){
@ selRow = null;
@ this.className = this.className.replace(" sel", "");
|
| ︙ | ︙ |