Fossil SCM
A more invasive implementation of the convenience "copy" button before date/time strings that tries to work around objections raised on the Forum.
Commit
68b27378f5f3e80e2819485a68e7a8c516cdd217f8ad22c92d0e236d3b2f8625
Parent
e39933757acd4c0…
6 files changed
+14
-9
+15
-11
+15
-9
+68
-63
+1
-1
+1
-1
+14
-9
| --- src/copybtn.js | ||
| +++ src/copybtn.js | ||
| @@ -18,13 +18,17 @@ | ||
| 18 | 18 | ** <cchLength>, respectively. Set <cchLength> to "-1" to explicitly remove the |
| 19 | 19 | ** previous copy length limit. |
| 20 | 20 | ** |
| 21 | 21 | ** HTML snippet for statically created buttons: |
| 22 | 22 | ** |
| 23 | -** <button class="copy-button" id="copy-<idTarget>" | |
| 24 | -** data-copytarget="<idTarget>" data-copylength="<cchLength>"> | |
| 25 | -** <span></span> | |
| 23 | +** <button | |
| 24 | +** class="copy-button" | |
| 25 | +** id="copy-<idTarget>" | |
| 26 | +** data-copytarget="<idTarget>" | |
| 27 | +** data-copylength="<cchLength>" | |
| 28 | +** data-content="<content>"> | |
| 29 | +** <span></span> | |
| 26 | 30 | ** </button> |
| 27 | 31 | */ |
| 28 | 32 | function makeCopyButton(idTarget,bFlipped,cchLength){ |
| 29 | 33 | var elButton = document.createElement("button"); |
| 30 | 34 | elButton.className = "copy-button"; |
| @@ -62,16 +66,17 @@ | ||
| 62 | 66 | e.stopPropagation(); |
| 63 | 67 | if( this.disabled ) return; /* This check is probably redundant. */ |
| 64 | 68 | var idTarget = this.getAttribute("data-copytarget"); |
| 65 | 69 | var elTarget = document.getElementById(idTarget); |
| 66 | 70 | if( elTarget ){ |
| 67 | - var text = elTarget.innerText.replace(/^\s+|\s+$/g,""); | |
| 68 | - var cchLength = parseInt(this.getAttribute("data-copylength")); | |
| 69 | - if( !isNaN(cchLength) && cchLength>0 ){ | |
| 70 | - text = text.slice(0,cchLength); /* Assume single-byte chars. */ | |
| 71 | - }else if( /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test(text) ){ | |
| 72 | - text = text.replace(' ', 'T'); | |
| 71 | + var text = this.getAttribute("data-content"); | |
| 72 | + if( !text ){ | |
| 73 | + text = elTarget.innerText.replace(/^\s+|\s+$/g,""); | |
| 74 | + var cchLength = parseInt(this.getAttribute("data-copylength")); | |
| 75 | + if( !isNaN(cchLength) && cchLength>0 ){ | |
| 76 | + text = text.slice(0,cchLength); /* Assume single-byte chars. */ | |
| 77 | + } | |
| 73 | 78 | } |
| 74 | 79 | copyTextToClipboard(text); |
| 75 | 80 | } |
| 76 | 81 | } |
| 77 | 82 | /* Create a temporary <textarea> element and copy the contents to clipboard. */ |
| 78 | 83 |
| --- src/copybtn.js | |
| +++ src/copybtn.js | |
| @@ -18,13 +18,17 @@ | |
| 18 | ** <cchLength>, respectively. Set <cchLength> to "-1" to explicitly remove the |
| 19 | ** previous copy length limit. |
| 20 | ** |
| 21 | ** HTML snippet for statically created buttons: |
| 22 | ** |
| 23 | ** <button class="copy-button" id="copy-<idTarget>" |
| 24 | ** data-copytarget="<idTarget>" data-copylength="<cchLength>"> |
| 25 | ** <span></span> |
| 26 | ** </button> |
| 27 | */ |
| 28 | function makeCopyButton(idTarget,bFlipped,cchLength){ |
| 29 | var elButton = document.createElement("button"); |
| 30 | elButton.className = "copy-button"; |
| @@ -62,16 +66,17 @@ | |
| 62 | e.stopPropagation(); |
| 63 | if( this.disabled ) return; /* This check is probably redundant. */ |
| 64 | var idTarget = this.getAttribute("data-copytarget"); |
| 65 | var elTarget = document.getElementById(idTarget); |
| 66 | if( elTarget ){ |
| 67 | var text = elTarget.innerText.replace(/^\s+|\s+$/g,""); |
| 68 | var cchLength = parseInt(this.getAttribute("data-copylength")); |
| 69 | if( !isNaN(cchLength) && cchLength>0 ){ |
| 70 | text = text.slice(0,cchLength); /* Assume single-byte chars. */ |
| 71 | }else if( /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test(text) ){ |
| 72 | text = text.replace(' ', 'T'); |
| 73 | } |
| 74 | copyTextToClipboard(text); |
| 75 | } |
| 76 | } |
| 77 | /* Create a temporary <textarea> element and copy the contents to clipboard. */ |
| 78 |
| --- src/copybtn.js | |
| +++ src/copybtn.js | |
| @@ -18,13 +18,17 @@ | |
| 18 | ** <cchLength>, respectively. Set <cchLength> to "-1" to explicitly remove the |
| 19 | ** previous copy length limit. |
| 20 | ** |
| 21 | ** HTML snippet for statically created buttons: |
| 22 | ** |
| 23 | ** <button |
| 24 | ** class="copy-button" |
| 25 | ** id="copy-<idTarget>" |
| 26 | ** data-copytarget="<idTarget>" |
| 27 | ** data-copylength="<cchLength>" |
| 28 | ** data-content="<content>"> |
| 29 | ** <span></span> |
| 30 | ** </button> |
| 31 | */ |
| 32 | function makeCopyButton(idTarget,bFlipped,cchLength){ |
| 33 | var elButton = document.createElement("button"); |
| 34 | elButton.className = "copy-button"; |
| @@ -62,16 +66,17 @@ | |
| 66 | e.stopPropagation(); |
| 67 | if( this.disabled ) return; /* This check is probably redundant. */ |
| 68 | var idTarget = this.getAttribute("data-copytarget"); |
| 69 | var elTarget = document.getElementById(idTarget); |
| 70 | if( elTarget ){ |
| 71 | var text = this.getAttribute("data-content"); |
| 72 | if( !text ){ |
| 73 | text = elTarget.innerText.replace(/^\s+|\s+$/g,""); |
| 74 | var cchLength = parseInt(this.getAttribute("data-copylength")); |
| 75 | if( !isNaN(cchLength) && cchLength>0 ){ |
| 76 | text = text.slice(0,cchLength); /* Assume single-byte chars. */ |
| 77 | } |
| 78 | } |
| 79 | copyTextToClipboard(text); |
| 80 | } |
| 81 | } |
| 82 | /* Create a temporary <textarea> element and copy the contents to clipboard. */ |
| 83 |
+15
-11
| --- src/forum.c | ||
| +++ src/forum.c | ||
| @@ -763,12 +763,13 @@ | ||
| 763 | 763 | char *zQuery /* Common query string */ |
| 764 | 764 | ){ |
| 765 | 765 | char *zPosterName; /* Name of user who originally made this post */ |
| 766 | 766 | char *zEditorName; /* Name of user who provided the current edit */ |
| 767 | 767 | char *zDate; /* The time/date string */ |
| 768 | - char *zDateTag; /* Tag for the time/date text */ | |
| 768 | + char *zDateZulu; /* The date/time string in Zulul time */ | |
| 769 | 769 | char *zHist; /* History query string */ |
| 770 | + char *z; | |
| 770 | 771 | Manifest *pManifest; /* Manifest comprising the current post */ |
| 771 | 772 | int bPrivate; /* True for posts awaiting moderation */ |
| 772 | 773 | int bSameUser; /* True if author is also the reader */ |
| 773 | 774 | int iIndent; /* Indent level */ |
| 774 | 775 | int iClosed; /* True if (sub)thread is closed */ |
| @@ -802,44 +803,47 @@ | ||
| 802 | 803 | ** varies depending on whether: |
| 803 | 804 | ** * The post is unedited |
| 804 | 805 | ** * The post was last edited by the original author |
| 805 | 806 | ** * The post was last edited by a different person |
| 806 | 807 | */ |
| 807 | - if( p->pEditHead ){ | |
| 808 | - zDate = db_text(0, "SELECT datetime(%.17g,toLocal())", | |
| 809 | - p->pEditHead->rDate); | |
| 810 | - }else{ | |
| 808 | + if( !p->pEditHead ){ | |
| 811 | 809 | zPosterName = forum_post_display_name(p, pManifest); |
| 812 | 810 | zEditorName = zPosterName; |
| 813 | 811 | } |
| 814 | 812 | zDate = db_text(0, "SELECT datetime(%.17g,toLocal())", p->rDate); |
| 815 | - zDateTag = mprintf("datetag-%d", p->fpid); | |
| 813 | + zDateZulu = db_text(0, | |
| 814 | + "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ',%.17g)", | |
| 815 | + p->rDate); | |
| 816 | 816 | if( p->pEditPrev ){ |
| 817 | 817 | zPosterName = forum_post_display_name(p->pEditHead, 0); |
| 818 | 818 | zEditorName = forum_post_display_name(p, pManifest); |
| 819 | 819 | zHist = bHist ? "" : zQuery[0]==0 ? "?hist" : "&hist"; |
| 820 | 820 | @ <h3 class='forumPostHdr'>(%d(p->sid)\ |
| 821 | 821 | @ .%0*d(fossil_num_digits(p->nEdit))(p->rev)) |
| 822 | 822 | if( fossil_strcmp(zPosterName, zEditorName)==0 ){ |
| 823 | - @ By %s(zPosterName) on %h(zDate) edited from \ | |
| 823 | + @ By %s(zPosterName) on \ | |
| 824 | + style_copy_button(1, 0, 0, 0, zDateZulu, "%h", zDate); | |
| 825 | + @  edited from \ | |
| 824 | 826 | @ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ |
| 825 | 827 | @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> |
| 826 | 828 | }else{ |
| 827 | 829 | @ Originally by %s(zPosterName) \ |
| 828 | - @ with edits by %s(zEditorName) on %h(zDate) from \ | |
| 830 | + @ with edits by %s(zEditorName) on \ | |
| 831 | + style_copy_button(1, 0, 0, 0, zDateZulu, "%h", zDate); | |
| 832 | + @  from \ | |
| 829 | 833 | @ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ |
| 830 | 834 | @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> |
| 831 | 835 | } |
| 832 | 836 | }else{ |
| 833 | 837 | zPosterName = forum_post_display_name(p, pManifest); |
| 834 | 838 | @ <h3 class='forumPostHdr'>(%d(p->sid)) |
| 835 | 839 | @ By %s(zPosterName) on \ |
| 836 | - style_copy_button(1, zDateTag, 0, 0, "%h", zDate); | |
| 837 | - cgi_printf(" "); | |
| 840 | + style_copy_button(1, 0, 0, 0, zDateZulu, "%h", zDate); | |
| 841 | + cgi_append_content(" ", 1); | |
| 838 | 842 | } |
| 839 | 843 | fossil_free(zDate); |
| 840 | - fossil_free(zDateTag); | |
| 844 | + fossil_free(zDateZulu); | |
| 841 | 845 | |
| 842 | 846 | |
| 843 | 847 | /* If debugging is enabled, link to the artifact page. */ |
| 844 | 848 | if( g.perm.Debug ){ |
| 845 | 849 | @ <span class="debug">\ |
| 846 | 850 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -763,12 +763,13 @@ | |
| 763 | char *zQuery /* Common query string */ |
| 764 | ){ |
| 765 | char *zPosterName; /* Name of user who originally made this post */ |
| 766 | char *zEditorName; /* Name of user who provided the current edit */ |
| 767 | char *zDate; /* The time/date string */ |
| 768 | char *zDateTag; /* Tag for the time/date text */ |
| 769 | char *zHist; /* History query string */ |
| 770 | Manifest *pManifest; /* Manifest comprising the current post */ |
| 771 | int bPrivate; /* True for posts awaiting moderation */ |
| 772 | int bSameUser; /* True if author is also the reader */ |
| 773 | int iIndent; /* Indent level */ |
| 774 | int iClosed; /* True if (sub)thread is closed */ |
| @@ -802,44 +803,47 @@ | |
| 802 | ** varies depending on whether: |
| 803 | ** * The post is unedited |
| 804 | ** * The post was last edited by the original author |
| 805 | ** * The post was last edited by a different person |
| 806 | */ |
| 807 | if( p->pEditHead ){ |
| 808 | zDate = db_text(0, "SELECT datetime(%.17g,toLocal())", |
| 809 | p->pEditHead->rDate); |
| 810 | }else{ |
| 811 | zPosterName = forum_post_display_name(p, pManifest); |
| 812 | zEditorName = zPosterName; |
| 813 | } |
| 814 | zDate = db_text(0, "SELECT datetime(%.17g,toLocal())", p->rDate); |
| 815 | zDateTag = mprintf("datetag-%d", p->fpid); |
| 816 | if( p->pEditPrev ){ |
| 817 | zPosterName = forum_post_display_name(p->pEditHead, 0); |
| 818 | zEditorName = forum_post_display_name(p, pManifest); |
| 819 | zHist = bHist ? "" : zQuery[0]==0 ? "?hist" : "&hist"; |
| 820 | @ <h3 class='forumPostHdr'>(%d(p->sid)\ |
| 821 | @ .%0*d(fossil_num_digits(p->nEdit))(p->rev)) |
| 822 | if( fossil_strcmp(zPosterName, zEditorName)==0 ){ |
| 823 | @ By %s(zPosterName) on %h(zDate) edited from \ |
| 824 | @ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ |
| 825 | @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> |
| 826 | }else{ |
| 827 | @ Originally by %s(zPosterName) \ |
| 828 | @ with edits by %s(zEditorName) on %h(zDate) from \ |
| 829 | @ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ |
| 830 | @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> |
| 831 | } |
| 832 | }else{ |
| 833 | zPosterName = forum_post_display_name(p, pManifest); |
| 834 | @ <h3 class='forumPostHdr'>(%d(p->sid)) |
| 835 | @ By %s(zPosterName) on \ |
| 836 | style_copy_button(1, zDateTag, 0, 0, "%h", zDate); |
| 837 | cgi_printf(" "); |
| 838 | } |
| 839 | fossil_free(zDate); |
| 840 | fossil_free(zDateTag); |
| 841 | |
| 842 | |
| 843 | /* If debugging is enabled, link to the artifact page. */ |
| 844 | if( g.perm.Debug ){ |
| 845 | @ <span class="debug">\ |
| 846 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -763,12 +763,13 @@ | |
| 763 | char *zQuery /* Common query string */ |
| 764 | ){ |
| 765 | char *zPosterName; /* Name of user who originally made this post */ |
| 766 | char *zEditorName; /* Name of user who provided the current edit */ |
| 767 | char *zDate; /* The time/date string */ |
| 768 | char *zDateZulu; /* The date/time string in Zulul time */ |
| 769 | char *zHist; /* History query string */ |
| 770 | char *z; |
| 771 | Manifest *pManifest; /* Manifest comprising the current post */ |
| 772 | int bPrivate; /* True for posts awaiting moderation */ |
| 773 | int bSameUser; /* True if author is also the reader */ |
| 774 | int iIndent; /* Indent level */ |
| 775 | int iClosed; /* True if (sub)thread is closed */ |
| @@ -802,44 +803,47 @@ | |
| 803 | ** varies depending on whether: |
| 804 | ** * The post is unedited |
| 805 | ** * The post was last edited by the original author |
| 806 | ** * The post was last edited by a different person |
| 807 | */ |
| 808 | if( !p->pEditHead ){ |
| 809 | zPosterName = forum_post_display_name(p, pManifest); |
| 810 | zEditorName = zPosterName; |
| 811 | } |
| 812 | zDate = db_text(0, "SELECT datetime(%.17g,toLocal())", p->rDate); |
| 813 | zDateZulu = db_text(0, |
| 814 | "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ',%.17g)", |
| 815 | p->rDate); |
| 816 | if( p->pEditPrev ){ |
| 817 | zPosterName = forum_post_display_name(p->pEditHead, 0); |
| 818 | zEditorName = forum_post_display_name(p, pManifest); |
| 819 | zHist = bHist ? "" : zQuery[0]==0 ? "?hist" : "&hist"; |
| 820 | @ <h3 class='forumPostHdr'>(%d(p->sid)\ |
| 821 | @ .%0*d(fossil_num_digits(p->nEdit))(p->rev)) |
| 822 | if( fossil_strcmp(zPosterName, zEditorName)==0 ){ |
| 823 | @ By %s(zPosterName) on \ |
| 824 | style_copy_button(1, 0, 0, 0, zDateZulu, "%h", zDate); |
| 825 | @  edited from \ |
| 826 | @ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ |
| 827 | @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> |
| 828 | }else{ |
| 829 | @ Originally by %s(zPosterName) \ |
| 830 | @ with edits by %s(zEditorName) on \ |
| 831 | style_copy_button(1, 0, 0, 0, zDateZulu, "%h", zDate); |
| 832 | @  from \ |
| 833 | @ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ |
| 834 | @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> |
| 835 | } |
| 836 | }else{ |
| 837 | zPosterName = forum_post_display_name(p, pManifest); |
| 838 | @ <h3 class='forumPostHdr'>(%d(p->sid)) |
| 839 | @ By %s(zPosterName) on \ |
| 840 | style_copy_button(1, 0, 0, 0, zDateZulu, "%h", zDate); |
| 841 | cgi_append_content(" ", 1); |
| 842 | } |
| 843 | fossil_free(zDate); |
| 844 | fossil_free(zDateZulu); |
| 845 | |
| 846 | |
| 847 | /* If debugging is enabled, link to the artifact page. */ |
| 848 | if( g.perm.Debug ){ |
| 849 | @ <span class="debug">\ |
| 850 |
+15
-9
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -945,12 +945,17 @@ | ||
| 945 | 945 | " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim", |
| 946 | 946 | rid |
| 947 | 947 | ); |
| 948 | 948 | isLeaf = !db_exists("SELECT 1 FROM plink WHERE pid=%d", rid); |
| 949 | 949 | db_prepare(&q1, |
| 950 | - "SELECT uuid, datetime(mtime,toLocal(),'subsec'), user, comment," | |
| 951 | - " datetime(omtime,toLocal(),'subsec'), mtime" | |
| 950 | + "SELECT uuid," /* 0 */ | |
| 951 | + " datetime(mtime,toLocal(),'subsec')," /* 1 */ | |
| 952 | + " user," /* 2 */ | |
| 953 | + " comment," /* 3 */ | |
| 954 | + " datetime(omtime,toLocal(),'subsec')," /* 4 */ | |
| 955 | + " mtime," /* 5 */ | |
| 956 | + " strftime('%%Y-%%m-%%dT%%H:%%M:%%fZ',mtime)" /* 6 */ | |
| 952 | 957 | " FROM blob, event" |
| 953 | 958 | " WHERE blob.rid=%d" |
| 954 | 959 | " AND event.objid=%d", |
| 955 | 960 | rid, rid |
| 956 | 961 | ); |
| @@ -963,11 +968,11 @@ | ||
| 963 | 968 | int nUuid = db_column_bytes(&q1, 0); |
| 964 | 969 | char *zEUser, *zEComment; |
| 965 | 970 | const char *zUser; |
| 966 | 971 | const char *zOrigUser; |
| 967 | 972 | const char *zComment; |
| 968 | - const char *zDate; | |
| 973 | + const char *zDate, *zZulu; | |
| 969 | 974 | const char *zOrigDate; |
| 970 | 975 | int okWiki = 0; |
| 971 | 976 | Blob wiki_read_links = BLOB_INITIALIZER; |
| 972 | 977 | Blob wiki_add_links = BLOB_INITIALIZER; |
| 973 | 978 | |
| @@ -985,10 +990,11 @@ | ||
| 985 | 990 | zUser = zEUser ? zEUser : zOrigUser; |
| 986 | 991 | zComment = db_column_text(&q1, 3); |
| 987 | 992 | zDate = db_column_text(&q1,1); |
| 988 | 993 | zOrigDate = db_column_text(&q1, 4); |
| 989 | 994 | if( zOrigDate==0 ) zOrigDate = zDate; |
| 995 | + zZulu = db_column_text(&q1, 6); | |
| 990 | 996 | @ <div class="section accordion">Overview</div> |
| 991 | 997 | @ <div class="accordion_panel"> |
| 992 | 998 | @ <table class="label-value"> |
| 993 | 999 | @ <tr><th>Comment:</th><td class="infoComment">\ |
| 994 | 1000 | @ %!W(zEComment?zEComment:zComment)</td></tr> |
| @@ -1027,11 +1033,11 @@ | ||
| 1027 | 1033 | " AND +tag.tagname GLOB 'sym-*'", rid); |
| 1028 | 1034 | while( db_step(&q2)==SQLITE_ROW ){ |
| 1029 | 1035 | const char *zTagName = db_column_text(&q2, 0); |
| 1030 | 1036 | if( fossil_strcmp(zTagName,zBrName)==0 ){ |
| 1031 | 1037 | cgi_printf(" | "); |
| 1032 | - style_copy_button(1, "name-br", 0, 0, "%z%h</a>", | |
| 1038 | + style_copy_button(1, "name-br", 0, 0, 0, "%z%h</a>", | |
| 1033 | 1039 | href("%R/timeline?r=%T&unhide",zTagName), zTagName); |
| 1034 | 1040 | cgi_printf("\n"); |
| 1035 | 1041 | if( wiki_tagid2("branch",zTagName)!=0 ){ |
| 1036 | 1042 | blob_appendf(&wiki_read_links, " | %z%h</a>", |
| 1037 | 1043 | href("%R/%s?name=branch/%h", |
| @@ -1063,18 +1069,18 @@ | ||
| 1063 | 1069 | @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a> |
| 1064 | 1070 | @ </td> |
| 1065 | 1071 | @ </tr> |
| 1066 | 1072 | |
| 1067 | 1073 | @ <tr><th>%s(hname_alg(nUuid)):</th><td> |
| 1068 | - style_copy_button(1, "hash-ci", 0, 2, "%.32s<wbr>%s", zUuid, zUuid+32); | |
| 1074 | + style_copy_button(1, "hash-ci", 0, 2, 0, "%.32s<wbr>%s", zUuid, zUuid+32); | |
| 1069 | 1075 | if( g.perm.Setup ){ |
| 1070 | 1076 | @ (Record ID: %d(rid)) |
| 1071 | 1077 | } |
| 1072 | 1078 | @ </td></tr> |
| 1073 | 1079 | @ <tr><th>User & Date:</th><td> |
| 1074 | 1080 | hyperlink_to_user(zUser,zDate," on "); |
| 1075 | - style_copy_button(1, "date-ci", 0, 0, | |
| 1081 | + style_copy_button(1, 0, 0, 0, zZulu, | |
| 1076 | 1082 | "%z%h</a>", href("%R/timeline?c=%T",zDate), zDate); |
| 1077 | 1083 | @ </td></tr> |
| 1078 | 1084 | // hyperlink_to_date(zDate, "</td></tr>"); |
| 1079 | 1085 | if( zEComment ){ |
| 1080 | 1086 | @ <tr><th>Original Comment:</th> |
| @@ -2429,11 +2435,11 @@ | ||
| 2429 | 2435 | } |
| 2430 | 2436 | style_header("Hex Artifact Content"); |
| 2431 | 2437 | zUuid = db_text("?","SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2432 | 2438 | etag_check(ETAG_HASH, zUuid); |
| 2433 | 2439 | @ <h2>Artifact |
| 2434 | - style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); | |
| 2440 | + style_copy_button(1, "hash-ar", 0, 2, 0, "%s", zUuid); | |
| 2435 | 2441 | if( g.perm.Setup ){ |
| 2436 | 2442 | @ (%d(rid)):</h2> |
| 2437 | 2443 | }else{ |
| 2438 | 2444 | @ :</h2> |
| 2439 | 2445 | } |
| @@ -2862,11 +2868,11 @@ | ||
| 2862 | 2868 | Blob path; |
| 2863 | 2869 | blob_zero(&path); |
| 2864 | 2870 | hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO); |
| 2865 | 2871 | zPath = blob_str(&path); |
| 2866 | 2872 | @ <h2>File %s(zPath) artifact \ |
| 2867 | - style_copy_button(1,"hash-fid",0,0,"%z%S</a> ", | |
| 2873 | + style_copy_button(1,"hash-fid",0,0,0,"%z%S</a> ", | |
| 2868 | 2874 | href("%R/info/%s",zUuid),zUuid); |
| 2869 | 2875 | if( isBranchCI ){ |
| 2870 | 2876 | @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> |
| 2871 | 2877 | }else if( isSymbolicCI ){ |
| 2872 | 2878 | @ part of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2> |
| @@ -2886,11 +2892,11 @@ | ||
| 2886 | 2892 | } |
| 2887 | 2893 | blob_init(&downloadName, zName, -1); |
| 2888 | 2894 | objType = OBJTYPE_CONTENT; |
| 2889 | 2895 | }else{ |
| 2890 | 2896 | @ <h2>Artifact |
| 2891 | - style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); | |
| 2897 | + style_copy_button(1, "hash-ar", 0, 2, 0, "%s", zUuid); | |
| 2892 | 2898 | if( g.perm.Setup ){ |
| 2893 | 2899 | @ (%d(rid)):</h2> |
| 2894 | 2900 | }else{ |
| 2895 | 2901 | @ :</h2> |
| 2896 | 2902 | } |
| 2897 | 2903 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -945,12 +945,17 @@ | |
| 945 | " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim", |
| 946 | rid |
| 947 | ); |
| 948 | isLeaf = !db_exists("SELECT 1 FROM plink WHERE pid=%d", rid); |
| 949 | db_prepare(&q1, |
| 950 | "SELECT uuid, datetime(mtime,toLocal(),'subsec'), user, comment," |
| 951 | " datetime(omtime,toLocal(),'subsec'), mtime" |
| 952 | " FROM blob, event" |
| 953 | " WHERE blob.rid=%d" |
| 954 | " AND event.objid=%d", |
| 955 | rid, rid |
| 956 | ); |
| @@ -963,11 +968,11 @@ | |
| 963 | int nUuid = db_column_bytes(&q1, 0); |
| 964 | char *zEUser, *zEComment; |
| 965 | const char *zUser; |
| 966 | const char *zOrigUser; |
| 967 | const char *zComment; |
| 968 | const char *zDate; |
| 969 | const char *zOrigDate; |
| 970 | int okWiki = 0; |
| 971 | Blob wiki_read_links = BLOB_INITIALIZER; |
| 972 | Blob wiki_add_links = BLOB_INITIALIZER; |
| 973 | |
| @@ -985,10 +990,11 @@ | |
| 985 | zUser = zEUser ? zEUser : zOrigUser; |
| 986 | zComment = db_column_text(&q1, 3); |
| 987 | zDate = db_column_text(&q1,1); |
| 988 | zOrigDate = db_column_text(&q1, 4); |
| 989 | if( zOrigDate==0 ) zOrigDate = zDate; |
| 990 | @ <div class="section accordion">Overview</div> |
| 991 | @ <div class="accordion_panel"> |
| 992 | @ <table class="label-value"> |
| 993 | @ <tr><th>Comment:</th><td class="infoComment">\ |
| 994 | @ %!W(zEComment?zEComment:zComment)</td></tr> |
| @@ -1027,11 +1033,11 @@ | |
| 1027 | " AND +tag.tagname GLOB 'sym-*'", rid); |
| 1028 | while( db_step(&q2)==SQLITE_ROW ){ |
| 1029 | const char *zTagName = db_column_text(&q2, 0); |
| 1030 | if( fossil_strcmp(zTagName,zBrName)==0 ){ |
| 1031 | cgi_printf(" | "); |
| 1032 | style_copy_button(1, "name-br", 0, 0, "%z%h</a>", |
| 1033 | href("%R/timeline?r=%T&unhide",zTagName), zTagName); |
| 1034 | cgi_printf("\n"); |
| 1035 | if( wiki_tagid2("branch",zTagName)!=0 ){ |
| 1036 | blob_appendf(&wiki_read_links, " | %z%h</a>", |
| 1037 | href("%R/%s?name=branch/%h", |
| @@ -1063,18 +1069,18 @@ | |
| 1063 | @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a> |
| 1064 | @ </td> |
| 1065 | @ </tr> |
| 1066 | |
| 1067 | @ <tr><th>%s(hname_alg(nUuid)):</th><td> |
| 1068 | style_copy_button(1, "hash-ci", 0, 2, "%.32s<wbr>%s", zUuid, zUuid+32); |
| 1069 | if( g.perm.Setup ){ |
| 1070 | @ (Record ID: %d(rid)) |
| 1071 | } |
| 1072 | @ </td></tr> |
| 1073 | @ <tr><th>User & Date:</th><td> |
| 1074 | hyperlink_to_user(zUser,zDate," on "); |
| 1075 | style_copy_button(1, "date-ci", 0, 0, |
| 1076 | "%z%h</a>", href("%R/timeline?c=%T",zDate), zDate); |
| 1077 | @ </td></tr> |
| 1078 | // hyperlink_to_date(zDate, "</td></tr>"); |
| 1079 | if( zEComment ){ |
| 1080 | @ <tr><th>Original Comment:</th> |
| @@ -2429,11 +2435,11 @@ | |
| 2429 | } |
| 2430 | style_header("Hex Artifact Content"); |
| 2431 | zUuid = db_text("?","SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2432 | etag_check(ETAG_HASH, zUuid); |
| 2433 | @ <h2>Artifact |
| 2434 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2435 | if( g.perm.Setup ){ |
| 2436 | @ (%d(rid)):</h2> |
| 2437 | }else{ |
| 2438 | @ :</h2> |
| 2439 | } |
| @@ -2862,11 +2868,11 @@ | |
| 2862 | Blob path; |
| 2863 | blob_zero(&path); |
| 2864 | hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO); |
| 2865 | zPath = blob_str(&path); |
| 2866 | @ <h2>File %s(zPath) artifact \ |
| 2867 | style_copy_button(1,"hash-fid",0,0,"%z%S</a> ", |
| 2868 | href("%R/info/%s",zUuid),zUuid); |
| 2869 | if( isBranchCI ){ |
| 2870 | @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> |
| 2871 | }else if( isSymbolicCI ){ |
| 2872 | @ part of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2> |
| @@ -2886,11 +2892,11 @@ | |
| 2886 | } |
| 2887 | blob_init(&downloadName, zName, -1); |
| 2888 | objType = OBJTYPE_CONTENT; |
| 2889 | }else{ |
| 2890 | @ <h2>Artifact |
| 2891 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2892 | if( g.perm.Setup ){ |
| 2893 | @ (%d(rid)):</h2> |
| 2894 | }else{ |
| 2895 | @ :</h2> |
| 2896 | } |
| 2897 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -945,12 +945,17 @@ | |
| 945 | " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim", |
| 946 | rid |
| 947 | ); |
| 948 | isLeaf = !db_exists("SELECT 1 FROM plink WHERE pid=%d", rid); |
| 949 | db_prepare(&q1, |
| 950 | "SELECT uuid," /* 0 */ |
| 951 | " datetime(mtime,toLocal(),'subsec')," /* 1 */ |
| 952 | " user," /* 2 */ |
| 953 | " comment," /* 3 */ |
| 954 | " datetime(omtime,toLocal(),'subsec')," /* 4 */ |
| 955 | " mtime," /* 5 */ |
| 956 | " strftime('%%Y-%%m-%%dT%%H:%%M:%%fZ',mtime)" /* 6 */ |
| 957 | " FROM blob, event" |
| 958 | " WHERE blob.rid=%d" |
| 959 | " AND event.objid=%d", |
| 960 | rid, rid |
| 961 | ); |
| @@ -963,11 +968,11 @@ | |
| 968 | int nUuid = db_column_bytes(&q1, 0); |
| 969 | char *zEUser, *zEComment; |
| 970 | const char *zUser; |
| 971 | const char *zOrigUser; |
| 972 | const char *zComment; |
| 973 | const char *zDate, *zZulu; |
| 974 | const char *zOrigDate; |
| 975 | int okWiki = 0; |
| 976 | Blob wiki_read_links = BLOB_INITIALIZER; |
| 977 | Blob wiki_add_links = BLOB_INITIALIZER; |
| 978 | |
| @@ -985,10 +990,11 @@ | |
| 990 | zUser = zEUser ? zEUser : zOrigUser; |
| 991 | zComment = db_column_text(&q1, 3); |
| 992 | zDate = db_column_text(&q1,1); |
| 993 | zOrigDate = db_column_text(&q1, 4); |
| 994 | if( zOrigDate==0 ) zOrigDate = zDate; |
| 995 | zZulu = db_column_text(&q1, 6); |
| 996 | @ <div class="section accordion">Overview</div> |
| 997 | @ <div class="accordion_panel"> |
| 998 | @ <table class="label-value"> |
| 999 | @ <tr><th>Comment:</th><td class="infoComment">\ |
| 1000 | @ %!W(zEComment?zEComment:zComment)</td></tr> |
| @@ -1027,11 +1033,11 @@ | |
| 1033 | " AND +tag.tagname GLOB 'sym-*'", rid); |
| 1034 | while( db_step(&q2)==SQLITE_ROW ){ |
| 1035 | const char *zTagName = db_column_text(&q2, 0); |
| 1036 | if( fossil_strcmp(zTagName,zBrName)==0 ){ |
| 1037 | cgi_printf(" | "); |
| 1038 | style_copy_button(1, "name-br", 0, 0, 0, "%z%h</a>", |
| 1039 | href("%R/timeline?r=%T&unhide",zTagName), zTagName); |
| 1040 | cgi_printf("\n"); |
| 1041 | if( wiki_tagid2("branch",zTagName)!=0 ){ |
| 1042 | blob_appendf(&wiki_read_links, " | %z%h</a>", |
| 1043 | href("%R/%s?name=branch/%h", |
| @@ -1063,18 +1069,18 @@ | |
| 1069 | @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a> |
| 1070 | @ </td> |
| 1071 | @ </tr> |
| 1072 | |
| 1073 | @ <tr><th>%s(hname_alg(nUuid)):</th><td> |
| 1074 | style_copy_button(1, "hash-ci", 0, 2, 0, "%.32s<wbr>%s", zUuid, zUuid+32); |
| 1075 | if( g.perm.Setup ){ |
| 1076 | @ (Record ID: %d(rid)) |
| 1077 | } |
| 1078 | @ </td></tr> |
| 1079 | @ <tr><th>User & Date:</th><td> |
| 1080 | hyperlink_to_user(zUser,zDate," on "); |
| 1081 | style_copy_button(1, 0, 0, 0, zZulu, |
| 1082 | "%z%h</a>", href("%R/timeline?c=%T",zDate), zDate); |
| 1083 | @ </td></tr> |
| 1084 | // hyperlink_to_date(zDate, "</td></tr>"); |
| 1085 | if( zEComment ){ |
| 1086 | @ <tr><th>Original Comment:</th> |
| @@ -2429,11 +2435,11 @@ | |
| 2435 | } |
| 2436 | style_header("Hex Artifact Content"); |
| 2437 | zUuid = db_text("?","SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2438 | etag_check(ETAG_HASH, zUuid); |
| 2439 | @ <h2>Artifact |
| 2440 | style_copy_button(1, "hash-ar", 0, 2, 0, "%s", zUuid); |
| 2441 | if( g.perm.Setup ){ |
| 2442 | @ (%d(rid)):</h2> |
| 2443 | }else{ |
| 2444 | @ :</h2> |
| 2445 | } |
| @@ -2862,11 +2868,11 @@ | |
| 2868 | Blob path; |
| 2869 | blob_zero(&path); |
| 2870 | hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO); |
| 2871 | zPath = blob_str(&path); |
| 2872 | @ <h2>File %s(zPath) artifact \ |
| 2873 | style_copy_button(1,"hash-fid",0,0,0,"%z%S</a> ", |
| 2874 | href("%R/info/%s",zUuid),zUuid); |
| 2875 | if( isBranchCI ){ |
| 2876 | @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> |
| 2877 | }else if( isSymbolicCI ){ |
| 2878 | @ part of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2> |
| @@ -2886,11 +2892,11 @@ | |
| 2892 | } |
| 2893 | blob_init(&downloadName, zName, -1); |
| 2894 | objType = OBJTYPE_CONTENT; |
| 2895 | }else{ |
| 2896 | @ <h2>Artifact |
| 2897 | style_copy_button(1, "hash-ar", 0, 2, 0, "%s", zUuid); |
| 2898 | if( g.perm.Setup ){ |
| 2899 | @ (%d(rid)):</h2> |
| 2900 | }else{ |
| 2901 | @ :</h2> |
| 2902 | } |
| 2903 |
+68
-63
| --- src/style.c | ||
| +++ src/style.c | ||
| @@ -474,24 +474,34 @@ | ||
| 474 | 474 | free(zVarName); |
| 475 | 475 | free(zUrl); |
| 476 | 476 | } |
| 477 | 477 | |
| 478 | 478 | /* |
| 479 | -** Output text generated from zTextFmt,... with a click-to-copy button | |
| 479 | +** Output text generated from zTextFmt with a click-to-copy button | |
| 480 | 480 | ** next to it. This routine assures that the copybtn.js Javascript module |
| 481 | -** is loaded. It generates HTML elements with the following IDs: | |
| 481 | +** is loaded. It generates HTML elements that show the click-to-copy | |
| 482 | +** button and text described by zTextFmt. | |
| 483 | +** | |
| 484 | +** The text generated by zTextFmt may contain HTML markup. That markup | |
| 485 | +** is not captured by the copy when the button is pressed - only the | |
| 486 | +** display text is copied. It is the caller's responsibility to ensure | |
| 487 | +** that the text generated by zTextFmt does not contain XSS vulnerabilities | |
| 488 | +** or other injections. Text that is under the control of users should | |
| 489 | +** be inserted into the display text using %h or similar. | |
| 482 | 490 | ** |
| 483 | -** zTargetId: The <span> wrapper around the generated text. | |
| 484 | -** copy-zTargetId: The <button> for the copy button. | |
| 491 | +** If zTargetId is provide (if zTargetId!=NULL) then zTargetId becomes | |
| 492 | +** the ID of the <span> that surrounds the display text and the ID | |
| 493 | +** of the <button> becomes "copy-%s" where %s is filled in by zTargetId. | |
| 494 | +** If zTargetId is NULL (the usual case) then IDs are selected automatically. | |
| 485 | 495 | ** |
| 486 | 496 | ** The bOutputCGI parameter is usually true, meaning that the output |
| 487 | 497 | ** is appended to the CGI result under construction. However, if |
| 488 | 498 | ** bOutputCGI is false, the generated HTML is written into memory |
| 489 | 499 | ** obtained from fossil_malloc() and returned. |
| 490 | 500 | ** |
| 491 | -** If the bFlipped argument is non-zero, the copy button is displayed | |
| 492 | -** after the text. Normally the copy button comes before. | |
| 501 | +** If the bAfter argument is non-zero, the copy button is displayed | |
| 502 | +** after the text. Normally the copy button comes before the text. | |
| 493 | 503 | ** |
| 494 | 504 | ** The mxLength argument defines the length of the substring of the |
| 495 | 505 | ** text to be copied to the clipboard: |
| 496 | 506 | ** |
| 497 | 507 | ** <= 0: Use all of the text |
| @@ -498,80 +508,75 @@ | ||
| 498 | 508 | ** >= 3: Truncate the text after mxLength bytes. |
| 499 | 509 | ** 1: Use the "hash-digits" setting as the limit. |
| 500 | 510 | ** 2: Use the length appropriate for URLs as the limit (defined at |
| 501 | 511 | ** compile-time by FOSSIL_HASH_DIGITS_URL, defaults to 16). |
| 502 | 512 | ** |
| 503 | -** Note: If the text to be copied is an ISO8601 date-time with a space | |
| 504 | -** separator between the date and the time, that space is converted to "T" | |
| 505 | -** for the copy. | |
| 513 | +** If zToCopy is not NULL then it holds the text that is to be copied | |
| 514 | +** when the button is pressed. If zToCopy is NULL, then zTextFmt is | |
| 515 | +** copied, with length restrictions specified by mxLength. If zToCopy | |
| 516 | +** is provided, mxLength is ignored. | |
| 506 | 517 | */ |
| 507 | 518 | char *style_copy_button( |
| 508 | 519 | int bOutputCGI, /* Don't return result, but send to cgi_printf(). */ |
| 509 | 520 | const char *zTargetId, /* HTML id of the text */ |
| 510 | - int bFlipped, /* True to put copy button after text */ | |
| 521 | + int bAfter, /* True to put copy button after text */ | |
| 511 | 522 | int mxLength, /* Length of text to copy to clipboard */ |
| 512 | - const char *zTextFmt, /* Formatting of the TEXT argument (htmlized). */ | |
| 523 | + const char *zToCopy, /* Optional text to copy when button is pressed */ | |
| 524 | + const char *zTextFmt, /* Formatting of the TEXT argument. */ | |
| 513 | 525 | ... /* Formatting parameters of the TEXT argument. */ |
| 514 | 526 | ){ |
| 515 | 527 | va_list ap; |
| 516 | 528 | char *zText; |
| 517 | 529 | char *zResult = 0; |
| 530 | + char *zId = 0; | |
| 531 | + Blob btn; | |
| 532 | + | |
| 518 | 533 | va_start(ap,zTextFmt); |
| 519 | 534 | zText = vmprintf(zTextFmt/*works-like:?*/,ap); |
| 520 | 535 | va_end(ap); |
| 521 | - if( mxLength==1 ) mxLength = hash_digits(0); | |
| 522 | - else if( mxLength==2 ) mxLength = hash_digits(1); | |
| 523 | - if( !bFlipped ){ | |
| 524 | - const char *zBtnFmt = | |
| 525 | - "<span class=\"nobr\">" | |
| 526 | - "<button " | |
| 527 | - "class=\"copy-button\" " | |
| 528 | - "id=\"copy-%h\" " | |
| 529 | - "data-copytarget=\"%h\" " | |
| 530 | - "data-copylength=\"%d\">" | |
| 531 | - "<span>" | |
| 532 | - "</span>" | |
| 533 | - "</button>" | |
| 534 | - "<span id=\"%h\">" | |
| 535 | - "%s" | |
| 536 | - "</span>" | |
| 537 | - "</span>"; | |
| 538 | - if( bOutputCGI ){ | |
| 539 | - cgi_printf( | |
| 540 | - zBtnFmt/*works-like:"%h%h%d%h%s"*/, | |
| 541 | - zTargetId,zTargetId,mxLength,zTargetId,zText); | |
| 542 | - }else{ | |
| 543 | - zResult = mprintf( | |
| 544 | - zBtnFmt/*works-like:"%h%h%d%h%s"*/, | |
| 545 | - zTargetId,zTargetId,mxLength,zTargetId,zText); | |
| 546 | - } | |
| 547 | - }else{ | |
| 548 | - const char *zBtnFmt = | |
| 549 | - "<span class=\"nobr\">" | |
| 550 | - "<span id=\"%h\">" | |
| 551 | - "%s" | |
| 552 | - "</span>" | |
| 553 | - "<button " | |
| 554 | - "class=\"copy-button copy-button-flipped\" " | |
| 555 | - "id=\"copy-%h\" " | |
| 556 | - "data-copytarget=\"%h\" " | |
| 557 | - "data-copylength=\"%d\">" | |
| 558 | - "<span>" | |
| 559 | - "</span>" | |
| 560 | - "</button>" | |
| 561 | - "</span>"; | |
| 562 | - if( bOutputCGI ){ | |
| 563 | - cgi_printf( | |
| 564 | - zBtnFmt/*works-like:"%h%s%h%h%d"*/, | |
| 565 | - zTargetId,zText,zTargetId,zTargetId,mxLength); | |
| 566 | - }else{ | |
| 567 | - zResult = mprintf( | |
| 568 | - zBtnFmt/*works-like:"%h%s%h%h%d"*/, | |
| 569 | - zTargetId,zText,zTargetId,zTargetId,mxLength); | |
| 570 | - } | |
| 571 | - } | |
| 572 | - free(zText); | |
| 536 | + | |
| 537 | + if( mxLength==1 ){ | |
| 538 | + mxLength = hash_digits(0); | |
| 539 | + }else if( mxLength==2 ){ | |
| 540 | + mxLength = hash_digits(1); | |
| 541 | + } | |
| 542 | + blob_init(&btn, 0, 0); | |
| 543 | + blob_append_string(&btn, "<span class=\"nobr\">"); | |
| 544 | + if( zTargetId==0 ){ | |
| 545 | + static unsigned int cnt = 0; | |
| 546 | + zId = mprintf("copy-button-id-%u", ++cnt); | |
| 547 | + zTargetId = zId; | |
| 548 | + } | |
| 549 | + if( bAfter ){ | |
| 550 | + blob_appendf(&btn, "<span id=\"%h\">%s</span>", | |
| 551 | + zTargetId, zText); | |
| 552 | + } | |
| 553 | + blob_appendf(&btn, | |
| 554 | + "<button " | |
| 555 | + "class=\"copy-button\" " | |
| 556 | + "id=\"copy-%h\" " | |
| 557 | + "data-copytarget=\"%h\" " | |
| 558 | + "data-copylength=\"%d\"", | |
| 559 | + zTargetId, zTargetId, mxLength); | |
| 560 | + if( zToCopy ){ | |
| 561 | + blob_appendf(&btn," data-content=\"%h\"", zToCopy); | |
| 562 | + } | |
| 563 | + blob_append_string(&btn, "><span></span></button>"); | |
| 564 | + if( !bAfter ){ | |
| 565 | + blob_appendf(&btn, "<span id=\"%h\">%s</span>", | |
| 566 | + zTargetId, zText); | |
| 567 | + } | |
| 568 | + blob_appendf(&btn,"</span>"); | |
| 569 | + if( bOutputCGI ){ | |
| 570 | + cgi_append_content(blob_str(&btn), blob_strlen(&btn)); | |
| 571 | + blob_reset(&btn); | |
| 572 | + zResult = 0; | |
| 573 | + }else{ | |
| 574 | + zResult = blob_str(&btn); | |
| 575 | + } | |
| 576 | + fossil_free(zText); | |
| 577 | + fossil_free(zId); | |
| 573 | 578 | builtin_request_js("copybtn.js"); |
| 574 | 579 | return zResult; |
| 575 | 580 | } |
| 576 | 581 | |
| 577 | 582 | /* |
| 578 | 583 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -474,24 +474,34 @@ | |
| 474 | free(zVarName); |
| 475 | free(zUrl); |
| 476 | } |
| 477 | |
| 478 | /* |
| 479 | ** Output text generated from zTextFmt,... with a click-to-copy button |
| 480 | ** next to it. This routine assures that the copybtn.js Javascript module |
| 481 | ** is loaded. It generates HTML elements with the following IDs: |
| 482 | ** |
| 483 | ** zTargetId: The <span> wrapper around the generated text. |
| 484 | ** copy-zTargetId: The <button> for the copy button. |
| 485 | ** |
| 486 | ** The bOutputCGI parameter is usually true, meaning that the output |
| 487 | ** is appended to the CGI result under construction. However, if |
| 488 | ** bOutputCGI is false, the generated HTML is written into memory |
| 489 | ** obtained from fossil_malloc() and returned. |
| 490 | ** |
| 491 | ** If the bFlipped argument is non-zero, the copy button is displayed |
| 492 | ** after the text. Normally the copy button comes before. |
| 493 | ** |
| 494 | ** The mxLength argument defines the length of the substring of the |
| 495 | ** text to be copied to the clipboard: |
| 496 | ** |
| 497 | ** <= 0: Use all of the text |
| @@ -498,80 +508,75 @@ | |
| 498 | ** >= 3: Truncate the text after mxLength bytes. |
| 499 | ** 1: Use the "hash-digits" setting as the limit. |
| 500 | ** 2: Use the length appropriate for URLs as the limit (defined at |
| 501 | ** compile-time by FOSSIL_HASH_DIGITS_URL, defaults to 16). |
| 502 | ** |
| 503 | ** Note: If the text to be copied is an ISO8601 date-time with a space |
| 504 | ** separator between the date and the time, that space is converted to "T" |
| 505 | ** for the copy. |
| 506 | */ |
| 507 | char *style_copy_button( |
| 508 | int bOutputCGI, /* Don't return result, but send to cgi_printf(). */ |
| 509 | const char *zTargetId, /* HTML id of the text */ |
| 510 | int bFlipped, /* True to put copy button after text */ |
| 511 | int mxLength, /* Length of text to copy to clipboard */ |
| 512 | const char *zTextFmt, /* Formatting of the TEXT argument (htmlized). */ |
| 513 | ... /* Formatting parameters of the TEXT argument. */ |
| 514 | ){ |
| 515 | va_list ap; |
| 516 | char *zText; |
| 517 | char *zResult = 0; |
| 518 | va_start(ap,zTextFmt); |
| 519 | zText = vmprintf(zTextFmt/*works-like:?*/,ap); |
| 520 | va_end(ap); |
| 521 | if( mxLength==1 ) mxLength = hash_digits(0); |
| 522 | else if( mxLength==2 ) mxLength = hash_digits(1); |
| 523 | if( !bFlipped ){ |
| 524 | const char *zBtnFmt = |
| 525 | "<span class=\"nobr\">" |
| 526 | "<button " |
| 527 | "class=\"copy-button\" " |
| 528 | "id=\"copy-%h\" " |
| 529 | "data-copytarget=\"%h\" " |
| 530 | "data-copylength=\"%d\">" |
| 531 | "<span>" |
| 532 | "</span>" |
| 533 | "</button>" |
| 534 | "<span id=\"%h\">" |
| 535 | "%s" |
| 536 | "</span>" |
| 537 | "</span>"; |
| 538 | if( bOutputCGI ){ |
| 539 | cgi_printf( |
| 540 | zBtnFmt/*works-like:"%h%h%d%h%s"*/, |
| 541 | zTargetId,zTargetId,mxLength,zTargetId,zText); |
| 542 | }else{ |
| 543 | zResult = mprintf( |
| 544 | zBtnFmt/*works-like:"%h%h%d%h%s"*/, |
| 545 | zTargetId,zTargetId,mxLength,zTargetId,zText); |
| 546 | } |
| 547 | }else{ |
| 548 | const char *zBtnFmt = |
| 549 | "<span class=\"nobr\">" |
| 550 | "<span id=\"%h\">" |
| 551 | "%s" |
| 552 | "</span>" |
| 553 | "<button " |
| 554 | "class=\"copy-button copy-button-flipped\" " |
| 555 | "id=\"copy-%h\" " |
| 556 | "data-copytarget=\"%h\" " |
| 557 | "data-copylength=\"%d\">" |
| 558 | "<span>" |
| 559 | "</span>" |
| 560 | "</button>" |
| 561 | "</span>"; |
| 562 | if( bOutputCGI ){ |
| 563 | cgi_printf( |
| 564 | zBtnFmt/*works-like:"%h%s%h%h%d"*/, |
| 565 | zTargetId,zText,zTargetId,zTargetId,mxLength); |
| 566 | }else{ |
| 567 | zResult = mprintf( |
| 568 | zBtnFmt/*works-like:"%h%s%h%h%d"*/, |
| 569 | zTargetId,zText,zTargetId,zTargetId,mxLength); |
| 570 | } |
| 571 | } |
| 572 | free(zText); |
| 573 | builtin_request_js("copybtn.js"); |
| 574 | return zResult; |
| 575 | } |
| 576 | |
| 577 | /* |
| 578 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -474,24 +474,34 @@ | |
| 474 | free(zVarName); |
| 475 | free(zUrl); |
| 476 | } |
| 477 | |
| 478 | /* |
| 479 | ** Output text generated from zTextFmt with a click-to-copy button |
| 480 | ** next to it. This routine assures that the copybtn.js Javascript module |
| 481 | ** is loaded. It generates HTML elements that show the click-to-copy |
| 482 | ** button and text described by zTextFmt. |
| 483 | ** |
| 484 | ** The text generated by zTextFmt may contain HTML markup. That markup |
| 485 | ** is not captured by the copy when the button is pressed - only the |
| 486 | ** display text is copied. It is the caller's responsibility to ensure |
| 487 | ** that the text generated by zTextFmt does not contain XSS vulnerabilities |
| 488 | ** or other injections. Text that is under the control of users should |
| 489 | ** be inserted into the display text using %h or similar. |
| 490 | ** |
| 491 | ** If zTargetId is provide (if zTargetId!=NULL) then zTargetId becomes |
| 492 | ** the ID of the <span> that surrounds the display text and the ID |
| 493 | ** of the <button> becomes "copy-%s" where %s is filled in by zTargetId. |
| 494 | ** If zTargetId is NULL (the usual case) then IDs are selected automatically. |
| 495 | ** |
| 496 | ** The bOutputCGI parameter is usually true, meaning that the output |
| 497 | ** is appended to the CGI result under construction. However, if |
| 498 | ** bOutputCGI is false, the generated HTML is written into memory |
| 499 | ** obtained from fossil_malloc() and returned. |
| 500 | ** |
| 501 | ** If the bAfter argument is non-zero, the copy button is displayed |
| 502 | ** after the text. Normally the copy button comes before the text. |
| 503 | ** |
| 504 | ** The mxLength argument defines the length of the substring of the |
| 505 | ** text to be copied to the clipboard: |
| 506 | ** |
| 507 | ** <= 0: Use all of the text |
| @@ -498,80 +508,75 @@ | |
| 508 | ** >= 3: Truncate the text after mxLength bytes. |
| 509 | ** 1: Use the "hash-digits" setting as the limit. |
| 510 | ** 2: Use the length appropriate for URLs as the limit (defined at |
| 511 | ** compile-time by FOSSIL_HASH_DIGITS_URL, defaults to 16). |
| 512 | ** |
| 513 | ** If zToCopy is not NULL then it holds the text that is to be copied |
| 514 | ** when the button is pressed. If zToCopy is NULL, then zTextFmt is |
| 515 | ** copied, with length restrictions specified by mxLength. If zToCopy |
| 516 | ** is provided, mxLength is ignored. |
| 517 | */ |
| 518 | char *style_copy_button( |
| 519 | int bOutputCGI, /* Don't return result, but send to cgi_printf(). */ |
| 520 | const char *zTargetId, /* HTML id of the text */ |
| 521 | int bAfter, /* True to put copy button after text */ |
| 522 | int mxLength, /* Length of text to copy to clipboard */ |
| 523 | const char *zToCopy, /* Optional text to copy when button is pressed */ |
| 524 | const char *zTextFmt, /* Formatting of the TEXT argument. */ |
| 525 | ... /* Formatting parameters of the TEXT argument. */ |
| 526 | ){ |
| 527 | va_list ap; |
| 528 | char *zText; |
| 529 | char *zResult = 0; |
| 530 | char *zId = 0; |
| 531 | Blob btn; |
| 532 | |
| 533 | va_start(ap,zTextFmt); |
| 534 | zText = vmprintf(zTextFmt/*works-like:?*/,ap); |
| 535 | va_end(ap); |
| 536 | |
| 537 | if( mxLength==1 ){ |
| 538 | mxLength = hash_digits(0); |
| 539 | }else if( mxLength==2 ){ |
| 540 | mxLength = hash_digits(1); |
| 541 | } |
| 542 | blob_init(&btn, 0, 0); |
| 543 | blob_append_string(&btn, "<span class=\"nobr\">"); |
| 544 | if( zTargetId==0 ){ |
| 545 | static unsigned int cnt = 0; |
| 546 | zId = mprintf("copy-button-id-%u", ++cnt); |
| 547 | zTargetId = zId; |
| 548 | } |
| 549 | if( bAfter ){ |
| 550 | blob_appendf(&btn, "<span id=\"%h\">%s</span>", |
| 551 | zTargetId, zText); |
| 552 | } |
| 553 | blob_appendf(&btn, |
| 554 | "<button " |
| 555 | "class=\"copy-button\" " |
| 556 | "id=\"copy-%h\" " |
| 557 | "data-copytarget=\"%h\" " |
| 558 | "data-copylength=\"%d\"", |
| 559 | zTargetId, zTargetId, mxLength); |
| 560 | if( zToCopy ){ |
| 561 | blob_appendf(&btn," data-content=\"%h\"", zToCopy); |
| 562 | } |
| 563 | blob_append_string(&btn, "><span></span></button>"); |
| 564 | if( !bAfter ){ |
| 565 | blob_appendf(&btn, "<span id=\"%h\">%s</span>", |
| 566 | zTargetId, zText); |
| 567 | } |
| 568 | blob_appendf(&btn,"</span>"); |
| 569 | if( bOutputCGI ){ |
| 570 | cgi_append_content(blob_str(&btn), blob_strlen(&btn)); |
| 571 | blob_reset(&btn); |
| 572 | zResult = 0; |
| 573 | }else{ |
| 574 | zResult = blob_str(&btn); |
| 575 | } |
| 576 | fossil_free(zText); |
| 577 | fossil_free(zId); |
| 578 | builtin_request_js("copybtn.js"); |
| 579 | return zResult; |
| 580 | } |
| 581 | |
| 582 | /* |
| 583 |
+1
-1
| --- src/th_main.c | ||
| +++ src/th_main.c | ||
| @@ -1194,11 +1194,11 @@ | ||
| 1194 | 1194 | if( argc==5 ){ |
| 1195 | 1195 | if( Th_ToInt(interp, argv[4], argl[4], ©length) ) return TH_ERROR; |
| 1196 | 1196 | } |
| 1197 | 1197 | zResult = style_copy_button( |
| 1198 | 1198 | /*bOutputCGI==*/0, /*TARGETID==*/(char*)argv[1], |
| 1199 | - flipped, copylength, "%h", /*TEXT==*/(char*)argv[3]); | |
| 1199 | + flipped, copylength, 0, "%h", /*TEXT==*/(char*)argv[3]); | |
| 1200 | 1200 | sendText(0,zResult, -1, 0); |
| 1201 | 1201 | free(zResult); |
| 1202 | 1202 | } |
| 1203 | 1203 | return TH_OK; |
| 1204 | 1204 | } |
| 1205 | 1205 |
| --- src/th_main.c | |
| +++ src/th_main.c | |
| @@ -1194,11 +1194,11 @@ | |
| 1194 | if( argc==5 ){ |
| 1195 | if( Th_ToInt(interp, argv[4], argl[4], ©length) ) return TH_ERROR; |
| 1196 | } |
| 1197 | zResult = style_copy_button( |
| 1198 | /*bOutputCGI==*/0, /*TARGETID==*/(char*)argv[1], |
| 1199 | flipped, copylength, "%h", /*TEXT==*/(char*)argv[3]); |
| 1200 | sendText(0,zResult, -1, 0); |
| 1201 | free(zResult); |
| 1202 | } |
| 1203 | return TH_OK; |
| 1204 | } |
| 1205 |
| --- src/th_main.c | |
| +++ src/th_main.c | |
| @@ -1194,11 +1194,11 @@ | |
| 1194 | if( argc==5 ){ |
| 1195 | if( Th_ToInt(interp, argv[4], argl[4], ©length) ) return TH_ERROR; |
| 1196 | } |
| 1197 | zResult = style_copy_button( |
| 1198 | /*bOutputCGI==*/0, /*TARGETID==*/(char*)argv[1], |
| 1199 | flipped, copylength, 0, "%h", /*TEXT==*/(char*)argv[3]); |
| 1200 | sendText(0,zResult, -1, 0); |
| 1201 | free(zResult); |
| 1202 | } |
| 1203 | return TH_OK; |
| 1204 | } |
| 1205 |
+1
-1
| --- tools/codecheck1.c | ||
| +++ tools/codecheck1.c | ||
| @@ -420,11 +420,11 @@ | ||
| 420 | 420 | { "pop3_print", 2, FMT_SAFE }, |
| 421 | 421 | { "smtp_send_line", 2, FMT_SAFE }, |
| 422 | 422 | { "smtp_server_send", 2, FMT_SAFE }, |
| 423 | 423 | { "socket_set_errmsg", 1, FMT_SAFE }, |
| 424 | 424 | { "ssl_set_errmsg", 1, FMT_SAFE }, |
| 425 | - { "style_copy_button", 5, FMT_SAFE }, | |
| 425 | + { "style_copy_button", 6, FMT_SAFE }, | |
| 426 | 426 | { "style_header", 1, FMT_HTML }, |
| 427 | 427 | { "style_set_current_page", 1, FMT_URL }, |
| 428 | 428 | { "style_submenu_element", 2, FMT_URL }, |
| 429 | 429 | { "style_submenu_sql", 3, FMT_SQL }, |
| 430 | 430 | { "textarea_attribute", 5, FMT_LIT }, |
| 431 | 431 |
| --- tools/codecheck1.c | |
| +++ tools/codecheck1.c | |
| @@ -420,11 +420,11 @@ | |
| 420 | { "pop3_print", 2, FMT_SAFE }, |
| 421 | { "smtp_send_line", 2, FMT_SAFE }, |
| 422 | { "smtp_server_send", 2, FMT_SAFE }, |
| 423 | { "socket_set_errmsg", 1, FMT_SAFE }, |
| 424 | { "ssl_set_errmsg", 1, FMT_SAFE }, |
| 425 | { "style_copy_button", 5, FMT_SAFE }, |
| 426 | { "style_header", 1, FMT_HTML }, |
| 427 | { "style_set_current_page", 1, FMT_URL }, |
| 428 | { "style_submenu_element", 2, FMT_URL }, |
| 429 | { "style_submenu_sql", 3, FMT_SQL }, |
| 430 | { "textarea_attribute", 5, FMT_LIT }, |
| 431 |
| --- tools/codecheck1.c | |
| +++ tools/codecheck1.c | |
| @@ -420,11 +420,11 @@ | |
| 420 | { "pop3_print", 2, FMT_SAFE }, |
| 421 | { "smtp_send_line", 2, FMT_SAFE }, |
| 422 | { "smtp_server_send", 2, FMT_SAFE }, |
| 423 | { "socket_set_errmsg", 1, FMT_SAFE }, |
| 424 | { "ssl_set_errmsg", 1, FMT_SAFE }, |
| 425 | { "style_copy_button", 6, FMT_SAFE }, |
| 426 | { "style_header", 1, FMT_HTML }, |
| 427 | { "style_set_current_page", 1, FMT_URL }, |
| 428 | { "style_submenu_element", 2, FMT_URL }, |
| 429 | { "style_submenu_sql", 3, FMT_SQL }, |
| 430 | { "textarea_attribute", 5, FMT_LIT }, |
| 431 |