Index: src/copybtn.js ================================================================== --- src/copybtn.js +++ src/copybtn.js @@ -58,11 +58,11 @@ this.style.transition = "opacity 400ms ease-in-out"; this.style.opacity = 0; var idTarget = this.getAttribute("data-copytarget"); var elTarget = document.getElementById(idTarget); if( elTarget ){ - var text = elTarget.innerText; + var text = elTarget.innerText.replace(/^\s+|\s+$/g,''); var cchLength = parseInt(this.getAttribute("data-copylength")); if( !isNaN(cchLength) && cchLength>0 ){ text = text.slice(0,cchLength); // Assume single-byte chars. } copyTextToClipboard(text); Index: src/graph.js ================================================================== --- src/graph.js +++ src/graph.js @@ -19,11 +19,11 @@ ** "scrollToSelect": BOOLEAN, // Scroll to selection on first render ** "nrail": INTEGER, // Number of vertical "rails" ** "baseUrl": TEXT, // Top-level URL ** "dwellTimeout": INTEGER, // Tooltip show delay in milliseconds ** "closeTimeout": INTEGER, // Tooltip close delay in milliseconds -** "digitHuman": INTEGER, // Limit of tooltip hashes ("hash-digits") +** "hashDigits": INTEGER, // Limit of tooltip hashes ("hash-digits") ** "rowinfo": ROWINFO-ARRAY } ** ** The rowinfo field is an array of structures, one per entry in the timeline, ** where each structure has the following fields: ** @@ -98,11 +98,11 @@ /* State information for the tooltip popup and its timers */ window.tooltipInfo = { dwellTimeout: 250, /* The tooltip dwell timeout. */ closeTimeout: 3000, /* The tooltip close timeout. */ - digitHuman: 10, /* Limit of tooltip hashes ("hash-digits"). */ + hashDigits: 16, /* Limit of tooltip hashes ("hash-digits"). */ idTimer: 0, /* The tooltip dwell timer id. */ idTimerClose: 0, /* The tooltip close timer id. */ ixHover: -1, /* The id of the element with the mouse. */ ixActive: -1, /* The id of the element with the tooltip. */ nodeHover: null, /* Graph node under mouse when ixHover==-2 */ @@ -143,11 +143,11 @@ function TimelineGraph(tx){ var topObj = document.getElementById("timelineTable"+tx.iTableId); amendCss(tx.circleNodes, tx.showArrowheads); tooltipInfo.dwellTimeout = tx.dwellTimeout tooltipInfo.closeTimeout = tx.closeTimeout - tooltipInfo.digitHuman = tx.digitHuman + tooltipInfo.hashDigits = tx.hashDigits topObj.onclick = clickOnGraph topObj.ondblclick = dblclickOnGraph topObj.onmousemove = function(e) { var ix = findTxIndex(e); topObj.style.cursor = (ix<0) ? "" : "pointer" @@ -597,11 +597,11 @@ var ix = -1 if( tooltipInfo.ixHover==-2 ){ ix = parseInt(tooltipInfo.nodeHover.id.match(/\d+$/)[0],10)-tx.iTopRow var h = tx.rowinfo[ix].h var dest = tx.baseUrl + "/info/" + h - h = h.slice(0,tooltipInfo.digitHuman); // Assume single-byte characters. + h = h.slice(0,tooltipInfo.hashDigits); // Assume single-byte characters. if( tx.fileDiff ){ html = "artifact "+h+"" }else{ html = "check-in "+h+"" } Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -763,11 +763,14 @@ " AND tag.tagid=tagxref.tagid " " AND +tag.tagname GLOB 'sym-*'", rid); while( db_step(&q2)==SQLITE_ROW ){ const char *zTagName = db_column_text(&q2, 0); if( fossil_strcmp(zTagName,zBrName)==0 ){ - @ | %z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName) + @ | + @   + @ %z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName) if( wiki_tagid2("branch",zTagName)!=0 ){ blob_appendf(&wiki_read_links, " | %z%h", href("%R/wiki?name=branch/%h",zTagName), zTagName); }else if( g.perm.Write && g.perm.WrWiki ){ blob_appendf(&wiki_add_links, " | %z%h", @@ -793,11 +796,14 @@ @ | %z(href("%R/fileage?name=%!S",zUuid))file ages @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders @ @ - @ %s(hname_alg(nUuid)):%.32s(zUuid)%s(zUuid+32) + @ %s(hname_alg(nUuid)): + @ + @  %.32s(zUuid)%s(zUuid+32) if( g.perm.Setup ){ @ (Record ID: %d(rid)) } @ @ User & Date: @@ -2181,14 +2187,20 @@ } zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); if( isFile ){ @

Latest version of file '%h(zName)':

style_submenu_element("Artifact", "%R/artifact/%S", zUuid); - }else if( g.perm.Setup ){ - @

Artifact %s(zUuid) (%d(rid)):

}else{ - @

Artifact %s(zUuid):

+ style_copy_button(); + @

Artifact + @ + if( g.perm.Setup ){ + @  %s(zUuid) (%d(rid)):

+ }else{ + @  %s(zUuid): + } } blob_zero(&downloadName); asText = P("txt")!=0; if( asText ) objdescFlags &= ~OBJDESC_BASE; objType = object_description(rid, objdescFlags, &downloadName); Index: src/printf.c ================================================================== --- src/printf.c +++ src/printf.c @@ -48,11 +48,11 @@ /* ** Return the number of artifact hash digits to display. The number is for ** human output if the bForUrl is false and is destined for a URL if ** bForUrl is false. */ -static int hashDigits(int bForUrl){ +int hash_digits(int bForUrl){ static int nDigitHuman = 0; static int nDigitUrl = 0; if( nDigitHuman==0 ){ nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS); if( nDigitHuman < 6 ) nDigitHuman = 6; @@ -66,11 +66,11 @@ /* ** Return the number of characters in a %S output. */ int length_of_S_display(void){ - return hashDigits(0); + return hash_digits(0); } /* ** Conversion types fall into various categories as defined by the ** following enumeration. @@ -679,11 +679,11 @@ if( bufpt==0 ){ bufpt = ""; }else if( xtype==etDYNSTRING ){ zExtra = bufpt; }else if( xtype==etSTRINGID ){ - precision = hashDigits(flag_altform2); + precision = hash_digits(flag_altform2); } length = StrNLen32(bufpt, limit); if( precision>=0 && precision", -1, 0); Th_Free(interp, azElem); } return TH_OK; } + +/* +** TH1 command: copybtn TARGETID TEXT ?COPYLENGTH? +** +** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js +** Javascript module, and generates HTML elements with the following IDs: +** +** TARGETID: The wrapper around TEXT. +** copy-TARGETID: The for the copy button. +** +** The optional COPYLENGTH argument defines the length of the substring of TEXT +** copied to clipboard: +** +** <= 0: No limit (default if the argument is omitted). +** >= 3: Truncate TEXT after COPYLENGTH (single-byte) characters. +** 1: Use the "hash-digits" setting as the limit. +** 2: Use the length appropriate for URLs as the limit (defined at +** compile-time by FOSSIL_HASH_DIGITS_URL, defaults to 16). +*/ +static int copybtnCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + if( argc!=3 && argc!=4 ){ + return Th_WrongNumArgs(interp, "copybtn TARGETID TEXT COPYLENGTH"); + } + if( enableOutput ){ + int copylength = 0; + char *zTargetId, *zText, *zResult; + if( argc==4 && Th_ToInt(interp, argv[3], argl[3], ©length) ){ + return TH_ERROR; + } + if( copylength==1 ) copylength = hash_digits(0); + else if( copylength==2 ) copylength = hash_digits(1); + zTargetId = htmlize((char*)argv[1], argl[1]); + zText = htmlize((char*)argv[2], argl[2]); + zResult = mprintf( + "" + "" + " " + "" + "%s" + "", + zTargetId, zTargetId, copylength, zTargetId, zText); + free(zTargetId); + free(zText); + style_copy_button(); + sendText(zResult, -1, 0); + free(zResult); + } + return TH_OK; +} /* ** TH1 command: linecount STRING MAX MIN ** ** Return one more than the number of \n characters in STRING. But @@ -2024,10 +2083,11 @@ {"anycap", anycapCmd, 0}, {"artifact", artifactCmd, 0}, {"cgiHeaderLine", cgiHeaderLineCmd, 0}, {"checkout", checkoutCmd, 0}, {"combobox", comboboxCmd, 0}, + {"copybtn", copybtnCmd, 0}, {"date", dateCmd, 0}, {"decorate", wikiCmd, (void*)&aFlags[2]}, {"dir", dirCmd, 0}, {"enable_output", enableOutputCmd, 0}, {"encode64", encode64Cmd, 0}, Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -832,11 +832,10 @@ int fileDiff; /* True for file diff. False for check-in diff */ int omitDescenders; /* True to omit descenders */ int scrollToSelect; /* True to scroll to the selection */ int dwellTimeout; /* Milliseconds to wait for tooltips to show */ int closeTimeout; /* Milliseconds to wait for tooltips to close */ - int nDigitHuman; /* The "hash-digits" limit for tooltip hash prefixes */ u8 *aiMap; /* The rail map */ iRailPitch = atoi(PD("railpitch","0")); showArrowheads = skin_detail_boolean("timeline-arrowheads"); circleNodes = skin_detail_boolean("timeline-circle-nodes"); @@ -845,15 +844,10 @@ omitDescenders = (tmFlags & TIMELINE_DISJOINT)!=0; fileDiff = (tmFlags & TIMELINE_FILEDIFF)!=0; scrollToSelect = (tmFlags & TIMELINE_NOSCROLL)==0; dwellTimeout = atoi(db_get("timeline-dwelltime","100")); closeTimeout = atoi(db_get("timeline-closetime","250")); -/* Preprocessor definitions copied from src\printf.c. */ -#ifndef FOSSIL_HASH_DIGITS -# define FOSSIL_HASH_DIGITS 10 -#endif - nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS); @