Fossil SCM

Merge the revamped Copy Buttons.

florian 2025-08-15 04:50 trunk merge
Commit 63712b631c7e1e0268860fcea2fc6db20ff3959d314d7fd41b96417b8f2e46aa
+19 -22
--- src/copybtn.js
+++ src/copybtn.js
@@ -1,32 +1,34 @@
11
/* Manage "Copy Buttons" linked to target elements, to copy the text (or, parts
22
** thereof) of the target elements to the clipboard.
33
**
4
-** Newly created buttons are <span> elements with an SVG background icon,
5
-** defined by the "copy-button" class in the default CSS style sheet, and are
6
-** assigned the element ID "copy-<idTarget>".
7
-**
8
-** To simplify customization, the only properties modified for HTML-defined
9
-** buttons are the "onclick" handler, and the "transition" and "opacity" styles
10
-** (used for animation).
4
+** Newly created buttons are <button> elements plus a nested <span> element with
5
+** an SVG background icon, defined by the "copy-button" class in the default CSS
6
+** style sheet, and are assigned the element ID "copy-<idTarget>".
117
**
128
** For HTML-defined buttons, either initCopyButtonById(), or initCopyButton(),
139
** needs to be called to attach the "onclick" handler (done automatically from
14
-** a handler attached to the "DOMContentLoaded" event).
10
+** a handler attached to the "DOMContentLoaded" event). These functions create
11
+** the nested <span> element if the <button> element has no child nodes. Using
12
+** static HTML for the <span> element ensures the buttons are visible if there
13
+** are script errors, which may be useful for Fossil JS hackers (as good parts
14
+** of the Fossil web UI come down on JS errors, anyway).
1515
**
1616
** The initialization functions do not overwrite the "data-copytarget" and
1717
** "data-copylength" attributes with empty or null values for <idTarget> and
1818
** <cchLength>, respectively. Set <cchLength> to "-1" to explicitly remove the
1919
** previous copy length limit.
2020
**
2121
** HTML snippet for statically created buttons:
2222
**
23
-** <span class="copy-button" id="copy-<idTarget>"
24
-** data-copytarget="<idTarget>" data-copylength="<cchLength>"></span>
23
+** <button class="copy-button" id="copy-<idTarget>"
24
+** data-copytarget="<idTarget>" data-copylength="<cchLength>">
25
+** <span></span>
26
+** </button>
2527
*/
2628
function makeCopyButton(idTarget,bFlipped,cchLength){
27
- var elButton = document.createElement("span");
29
+ var elButton = document.createElement("button");
2830
elButton.className = "copy-button";
2931
if( bFlipped ) elButton.className += " copy-button-flipped";
3032
elButton.id = "copy-" + idTarget;
3133
initCopyButton(elButton,idTarget,cchLength);
3234
return elButton;
@@ -36,15 +38,18 @@
3638
var elButton = document.getElementById(idButton);
3739
if( elButton ) initCopyButton(elButton,idTarget,cchLength);
3840
return elButton;
3941
}
4042
function initCopyButton(elButton,idTarget,cchLength){
41
- elButton.style.transition = "";
42
- elButton.style.opacity = 1;
4343
if( idTarget ) elButton.setAttribute("data-copytarget",idTarget);
4444
if( cchLength ) elButton.setAttribute("data-copylength",cchLength);
4545
elButton.onclick = clickCopyButton;
46
+ /* Make sure the <button> contains a single nested <span>. */
47
+ if( elButton.childElementCount!=1 || elButton.firstChild.tagName!="SPAN" ){
48
+ while( elButton.firstChild ) elButton.removeChild(elButton.lastChild);
49
+ elButton.appendChild(document.createElement("span"));
50
+ }
4651
return elButton;
4752
}
4853
setTimeout(function(){
4954
var elButtons = document.getElementsByClassName("copy-button");
5055
for ( var i=0; i<elButtons.length; i++ ){
@@ -53,14 +58,11 @@
5358
},1);
5459
/* The onclick handler for the "Copy Button". */
5560
function clickCopyButton(e){
5661
e.preventDefault(); /* Mandatory for <a> and <button>. */
5762
e.stopPropagation();
58
- if( this.getAttribute("data-copylocked") ) return;
59
- this.setAttribute("data-copylocked","1");
60
- this.style.transition = "opacity 400ms ease-in-out";
61
- this.style.opacity = 0;
63
+ if( this.disabled ) return; /* This check is probably redundant. */
6264
var idTarget = this.getAttribute("data-copytarget");
6365
var elTarget = document.getElementById(idTarget);
6466
if( elTarget ){
6567
var text = elTarget.innerText.replace(/^\s+|\s+$/g,"");
6668
var cchLength = parseInt(this.getAttribute("data-copylength"));
@@ -67,15 +69,10 @@
6769
if( !isNaN(cchLength) && cchLength>0 ){
6870
text = text.slice(0,cchLength); /* Assume single-byte chars. */
6971
}
7072
copyTextToClipboard(text);
7173
}
72
- setTimeout(function(){
73
- this.style.transition = "";
74
- this.style.opacity = 1;
75
- this.removeAttribute("data-copylocked");
76
- }.bind(this),400);
7774
}
7875
/* Create a temporary <textarea> element and copy the contents to clipboard. */
7976
function copyTextToClipboard(text){
8077
if( window.clipboardData && window.clipboardData.setData ){
8178
window.clipboardData.setData("Text",text);
8279
--- src/copybtn.js
+++ src/copybtn.js
@@ -1,32 +1,34 @@
1 /* Manage "Copy Buttons" linked to target elements, to copy the text (or, parts
2 ** thereof) of the target elements to the clipboard.
3 **
4 ** Newly created buttons are <span> elements with an SVG background icon,
5 ** defined by the "copy-button" class in the default CSS style sheet, and are
6 ** assigned the element ID "copy-<idTarget>".
7 **
8 ** To simplify customization, the only properties modified for HTML-defined
9 ** buttons are the "onclick" handler, and the "transition" and "opacity" styles
10 ** (used for animation).
11 **
12 ** For HTML-defined buttons, either initCopyButtonById(), or initCopyButton(),
13 ** needs to be called to attach the "onclick" handler (done automatically from
14 ** a handler attached to the "DOMContentLoaded" event).
 
 
 
 
15 **
16 ** The initialization functions do not overwrite the "data-copytarget" and
17 ** "data-copylength" attributes with empty or null values for <idTarget> and
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 ** <span class="copy-button" id="copy-<idTarget>"
24 ** data-copytarget="<idTarget>" data-copylength="<cchLength>"></span>
 
 
25 */
26 function makeCopyButton(idTarget,bFlipped,cchLength){
27 var elButton = document.createElement("span");
28 elButton.className = "copy-button";
29 if( bFlipped ) elButton.className += " copy-button-flipped";
30 elButton.id = "copy-" + idTarget;
31 initCopyButton(elButton,idTarget,cchLength);
32 return elButton;
@@ -36,15 +38,18 @@
36 var elButton = document.getElementById(idButton);
37 if( elButton ) initCopyButton(elButton,idTarget,cchLength);
38 return elButton;
39 }
40 function initCopyButton(elButton,idTarget,cchLength){
41 elButton.style.transition = "";
42 elButton.style.opacity = 1;
43 if( idTarget ) elButton.setAttribute("data-copytarget",idTarget);
44 if( cchLength ) elButton.setAttribute("data-copylength",cchLength);
45 elButton.onclick = clickCopyButton;
 
 
 
 
 
46 return elButton;
47 }
48 setTimeout(function(){
49 var elButtons = document.getElementsByClassName("copy-button");
50 for ( var i=0; i<elButtons.length; i++ ){
@@ -53,14 +58,11 @@
53 },1);
54 /* The onclick handler for the "Copy Button". */
55 function clickCopyButton(e){
56 e.preventDefault(); /* Mandatory for <a> and <button>. */
57 e.stopPropagation();
58 if( this.getAttribute("data-copylocked") ) return;
59 this.setAttribute("data-copylocked","1");
60 this.style.transition = "opacity 400ms ease-in-out";
61 this.style.opacity = 0;
62 var idTarget = this.getAttribute("data-copytarget");
63 var elTarget = document.getElementById(idTarget);
64 if( elTarget ){
65 var text = elTarget.innerText.replace(/^\s+|\s+$/g,"");
66 var cchLength = parseInt(this.getAttribute("data-copylength"));
@@ -67,15 +69,10 @@
67 if( !isNaN(cchLength) && cchLength>0 ){
68 text = text.slice(0,cchLength); /* Assume single-byte chars. */
69 }
70 copyTextToClipboard(text);
71 }
72 setTimeout(function(){
73 this.style.transition = "";
74 this.style.opacity = 1;
75 this.removeAttribute("data-copylocked");
76 }.bind(this),400);
77 }
78 /* Create a temporary <textarea> element and copy the contents to clipboard. */
79 function copyTextToClipboard(text){
80 if( window.clipboardData && window.clipboardData.setData ){
81 window.clipboardData.setData("Text",text);
82
--- src/copybtn.js
+++ src/copybtn.js
@@ -1,32 +1,34 @@
1 /* Manage "Copy Buttons" linked to target elements, to copy the text (or, parts
2 ** thereof) of the target elements to the clipboard.
3 **
4 ** Newly created buttons are <button> elements plus a nested <span> element with
5 ** an SVG background icon, defined by the "copy-button" class in the default CSS
6 ** style sheet, and are assigned the element ID "copy-<idTarget>".
 
 
 
 
7 **
8 ** For HTML-defined buttons, either initCopyButtonById(), or initCopyButton(),
9 ** needs to be called to attach the "onclick" handler (done automatically from
10 ** a handler attached to the "DOMContentLoaded" event). These functions create
11 ** the nested <span> element if the <button> element has no child nodes. Using
12 ** static HTML for the <span> element ensures the buttons are visible if there
13 ** are script errors, which may be useful for Fossil JS hackers (as good parts
14 ** of the Fossil web UI come down on JS errors, anyway).
15 **
16 ** The initialization functions do not overwrite the "data-copytarget" and
17 ** "data-copylength" attributes with empty or null values for <idTarget> and
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";
31 if( bFlipped ) elButton.className += " copy-button-flipped";
32 elButton.id = "copy-" + idTarget;
33 initCopyButton(elButton,idTarget,cchLength);
34 return elButton;
@@ -36,15 +38,18 @@
38 var elButton = document.getElementById(idButton);
39 if( elButton ) initCopyButton(elButton,idTarget,cchLength);
40 return elButton;
41 }
42 function initCopyButton(elButton,idTarget,cchLength){
 
 
43 if( idTarget ) elButton.setAttribute("data-copytarget",idTarget);
44 if( cchLength ) elButton.setAttribute("data-copylength",cchLength);
45 elButton.onclick = clickCopyButton;
46 /* Make sure the <button> contains a single nested <span>. */
47 if( elButton.childElementCount!=1 || elButton.firstChild.tagName!="SPAN" ){
48 while( elButton.firstChild ) elButton.removeChild(elButton.lastChild);
49 elButton.appendChild(document.createElement("span"));
50 }
51 return elButton;
52 }
53 setTimeout(function(){
54 var elButtons = document.getElementsByClassName("copy-button");
55 for ( var i=0; i<elButtons.length; i++ ){
@@ -53,14 +58,11 @@
58 },1);
59 /* The onclick handler for the "Copy Button". */
60 function clickCopyButton(e){
61 e.preventDefault(); /* Mandatory for <a> and <button>. */
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"));
@@ -67,15 +69,10 @@
69 if( !isNaN(cchLength) && cchLength>0 ){
70 text = text.slice(0,cchLength); /* Assume single-byte chars. */
71 }
72 copyTextToClipboard(text);
73 }
 
 
 
 
 
74 }
75 /* Create a temporary <textarea> element and copy the contents to clipboard. */
76 function copyTextToClipboard(text){
77 if( window.clipboardData && window.clipboardData.setData ){
78 window.clipboardData.setData("Text",text);
79
+27 -8
--- src/default.css
+++ src/default.css
@@ -1136,19 +1136,40 @@
11361136
white-space: nowrap;
11371137
}
11381138
label[for] {
11391139
cursor: pointer;
11401140
}
1141
-.copy-button {
1142
- display: inline-block;
1141
+button.copy-button,
1142
+button.copy-button:hover,
1143
+button.copy-button:focus,
1144
+button.copy-button:active {
11431145
width: 14px;
11441146
height: 14px;
11451147
/*Note: .24em is slightly smaller than the average width of a normal space.*/
11461148
margin: -2px .24em 0 0;
11471149
padding: 0;
11481150
border: 0;
1151
+ outline: 0;
1152
+ background: none;
1153
+ font-size: inherit; /* Required for horizontal spacing. */
11491154
vertical-align: middle;
1155
+ user-select: none;
1156
+ cursor: pointer;
1157
+}
1158
+button.copy-button-flipped,
1159
+button.copy-button-flipped:hover,
1160
+button.copy-button-flipped:focus,
1161
+button.copy-button-flipped:active {
1162
+ margin: -2px 0 0 .24em;
1163
+}
1164
+button.copy-button span {
1165
+ display: block;
1166
+ width: 100%;
1167
+ height: 100%;
1168
+ margin: 0;
1169
+ padding: 0;
1170
+ border: 0;
11501171
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' \
11511172
viewBox='0,0,14,14'%3E%3Cpath style='fill:black;opacity:0' \
11521173
d='M14,14H0V0h14v14z'/%3E%3Cpath style='fill:rgb(240,240,240)' \
11531174
d='M1,0h6.6l2,2h1l3.4,3.4v8.6h-10v-2h-3z'/%3E%3Cpath style='fill:rgb(64,64,64)' \
11541175
d='M2,1h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \
@@ -1159,19 +1180,17 @@
11591180
d='M7,8h4v1h-4zm0,2h4v1h-4z'/%3E%3C/svg%3E");
11601181
background-repeat: no-repeat;
11611182
background-position: center;
11621183
cursor: pointer;
11631184
}
1164
-.copy-button.disabled {
1185
+button.copy-button:enabled:active span {
1186
+ background-size: 90%;
1187
+}
1188
+button.copy-button:disabled span {
11651189
filter: grayscale(1);
11661190
opacity: 0.4;
11671191
}
1168
-.copy-button-flipped {
1169
-/*Note: .16em is suitable for element grouping.*/
1170
- margin-left: .16em;
1171
- margin-right: 0;
1172
-}
11731192
.nobr {
11741193
white-space: nowrap;
11751194
}
11761195
.accordion {
11771196
cursor: pointer;
11781197
--- src/default.css
+++ src/default.css
@@ -1136,19 +1136,40 @@
1136 white-space: nowrap;
1137 }
1138 label[for] {
1139 cursor: pointer;
1140 }
1141 .copy-button {
1142 display: inline-block;
 
 
1143 width: 14px;
1144 height: 14px;
1145 /*Note: .24em is slightly smaller than the average width of a normal space.*/
1146 margin: -2px .24em 0 0;
1147 padding: 0;
1148 border: 0;
 
 
 
1149 vertical-align: middle;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1150 background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' \
1151 viewBox='0,0,14,14'%3E%3Cpath style='fill:black;opacity:0' \
1152 d='M14,14H0V0h14v14z'/%3E%3Cpath style='fill:rgb(240,240,240)' \
1153 d='M1,0h6.6l2,2h1l3.4,3.4v8.6h-10v-2h-3z'/%3E%3Cpath style='fill:rgb(64,64,64)' \
1154 d='M2,1h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \
@@ -1159,19 +1180,17 @@
1159 d='M7,8h4v1h-4zm0,2h4v1h-4z'/%3E%3C/svg%3E");
1160 background-repeat: no-repeat;
1161 background-position: center;
1162 cursor: pointer;
1163 }
1164 .copy-button.disabled {
 
 
 
1165 filter: grayscale(1);
1166 opacity: 0.4;
1167 }
1168 .copy-button-flipped {
1169 /*Note: .16em is suitable for element grouping.*/
1170 margin-left: .16em;
1171 margin-right: 0;
1172 }
1173 .nobr {
1174 white-space: nowrap;
1175 }
1176 .accordion {
1177 cursor: pointer;
1178
--- src/default.css
+++ src/default.css
@@ -1136,19 +1136,40 @@
1136 white-space: nowrap;
1137 }
1138 label[for] {
1139 cursor: pointer;
1140 }
1141 button.copy-button,
1142 button.copy-button:hover,
1143 button.copy-button:focus,
1144 button.copy-button:active {
1145 width: 14px;
1146 height: 14px;
1147 /*Note: .24em is slightly smaller than the average width of a normal space.*/
1148 margin: -2px .24em 0 0;
1149 padding: 0;
1150 border: 0;
1151 outline: 0;
1152 background: none;
1153 font-size: inherit; /* Required for horizontal spacing. */
1154 vertical-align: middle;
1155 user-select: none;
1156 cursor: pointer;
1157 }
1158 button.copy-button-flipped,
1159 button.copy-button-flipped:hover,
1160 button.copy-button-flipped:focus,
1161 button.copy-button-flipped:active {
1162 margin: -2px 0 0 .24em;
1163 }
1164 button.copy-button span {
1165 display: block;
1166 width: 100%;
1167 height: 100%;
1168 margin: 0;
1169 padding: 0;
1170 border: 0;
1171 background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' \
1172 viewBox='0,0,14,14'%3E%3Cpath style='fill:black;opacity:0' \
1173 d='M14,14H0V0h14v14z'/%3E%3Cpath style='fill:rgb(240,240,240)' \
1174 d='M1,0h6.6l2,2h1l3.4,3.4v8.6h-10v-2h-3z'/%3E%3Cpath style='fill:rgb(64,64,64)' \
1175 d='M2,1h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \
@@ -1159,19 +1180,17 @@
1180 d='M7,8h4v1h-4zm0,2h4v1h-4z'/%3E%3C/svg%3E");
1181 background-repeat: no-repeat;
1182 background-position: center;
1183 cursor: pointer;
1184 }
1185 button.copy-button:enabled:active span {
1186 background-size: 90%;
1187 }
1188 button.copy-button:disabled span {
1189 filter: grayscale(1);
1190 opacity: 0.4;
1191 }
 
 
 
 
 
1192 .nobr {
1193 white-space: nowrap;
1194 }
1195 .accordion {
1196 cursor: pointer;
1197
--- src/fossil.copybutton.js
+++ src/fossil.copybutton.js
@@ -42,13 +42,11 @@
4242
4343
.oncopy: an optional callback function which is added as an event
4444
listener for the 'text-copied' event (see below). There is
4545
functionally no difference from setting this option or adding a
4646
'text-copied' event listener to the element, and this option is
47
- considered to be a convenience form of that. For the sake of
48
- framework-level consistency, the default value is a callback
49
- which passes the copy button to fossil.dom.flashOnce().
47
+ considered to be a convenience form of that.
5048
5149
Note that this function's own defaultOptions object holds default
5250
values for some options. Any changes made to that object affect
5351
any future calls to this function.
5452
@@ -62,25 +60,21 @@
6260
member is an object with a "text" property holding the copied
6361
text. Other properties may be added in the future. The event is
6462
not fired if copying to the clipboard fails (e.g. is not
6563
available in the current environment).
6664
67
- As a special case, the copy button's click handler is suppressed
68
- (becomes a no-op) for as long as the element has the CSS class
69
- "disabled". This allows elements which cannot be disabled via
70
- HTML attributes, e.g. a SPAN, to act as a copy button while still
71
- providing a way to disable them.
65
+ The copy button's click handler is suppressed (becomes a no-op)
66
+ for as long as the element has the "disabled" attribute.
7267
7368
Returns the copy-initialized element.
7469
7570
Example:
7671
7772
const button = fossil.copyButton('#my-copy-button', {
7873
copyFromId: 'some-other-element-id'
7974
});
8075
button.addEventListener('text-copied',function(ev){
81
- fossil.dom.flashOnce(ev.target);
8276
console.debug("Copied text:",ev.detail.text);
8377
});
8478
*/
8579
F.copyButton = function f(e, opt){
8680
if('string'===typeof e){
@@ -103,11 +97,11 @@
10397
e.addEventListener(
10498
'click',
10599
function(ev){
106100
ev.preventDefault();
107101
ev.stopPropagation();
108
- if(e.classList.contains('disabled')) return;
102
+ if(e.disabled) return; /* This check is probably redundant. */
109103
const txt = extract.call(opt);
110104
if(txt && D.copyTextToClipboard(txt)){
111105
e.dispatchEvent(new CustomEvent('text-copied',{
112106
detail: {text: txt}
113107
}));
@@ -116,15 +110,19 @@
116110
false
117111
);
118112
if('function' === typeof opt.oncopy){
119113
e.addEventListener('text-copied', opt.oncopy, false);
120114
}
115
+ /* Make sure the <button> contains a single nested <span>. */
116
+ if(e.childElementCount!=1 || e.firstChild.tagName!='SPAN'){
117
+ D.append(D.clearElement(e), D.span());
118
+ }
121119
return e;
122120
};
123121
124122
F.copyButton.defaultOptions = {
125123
cssClass: 'copy-button',
126
- oncopy: D.flashOnce.eventHandler,
124
+ oncopy: undefined,
127125
style: {/*properties copied as-is into element.style*/}
128126
};
129127
130128
})(window.fossil);
131129
--- src/fossil.copybutton.js
+++ src/fossil.copybutton.js
@@ -42,13 +42,11 @@
42
43 .oncopy: an optional callback function which is added as an event
44 listener for the 'text-copied' event (see below). There is
45 functionally no difference from setting this option or adding a
46 'text-copied' event listener to the element, and this option is
47 considered to be a convenience form of that. For the sake of
48 framework-level consistency, the default value is a callback
49 which passes the copy button to fossil.dom.flashOnce().
50
51 Note that this function's own defaultOptions object holds default
52 values for some options. Any changes made to that object affect
53 any future calls to this function.
54
@@ -62,25 +60,21 @@
62 member is an object with a "text" property holding the copied
63 text. Other properties may be added in the future. The event is
64 not fired if copying to the clipboard fails (e.g. is not
65 available in the current environment).
66
67 As a special case, the copy button's click handler is suppressed
68 (becomes a no-op) for as long as the element has the CSS class
69 "disabled". This allows elements which cannot be disabled via
70 HTML attributes, e.g. a SPAN, to act as a copy button while still
71 providing a way to disable them.
72
73 Returns the copy-initialized element.
74
75 Example:
76
77 const button = fossil.copyButton('#my-copy-button', {
78 copyFromId: 'some-other-element-id'
79 });
80 button.addEventListener('text-copied',function(ev){
81 fossil.dom.flashOnce(ev.target);
82 console.debug("Copied text:",ev.detail.text);
83 });
84 */
85 F.copyButton = function f(e, opt){
86 if('string'===typeof e){
@@ -103,11 +97,11 @@
103 e.addEventListener(
104 'click',
105 function(ev){
106 ev.preventDefault();
107 ev.stopPropagation();
108 if(e.classList.contains('disabled')) return;
109 const txt = extract.call(opt);
110 if(txt && D.copyTextToClipboard(txt)){
111 e.dispatchEvent(new CustomEvent('text-copied',{
112 detail: {text: txt}
113 }));
@@ -116,15 +110,19 @@
116 false
117 );
118 if('function' === typeof opt.oncopy){
119 e.addEventListener('text-copied', opt.oncopy, false);
120 }
 
 
 
 
121 return e;
122 };
123
124 F.copyButton.defaultOptions = {
125 cssClass: 'copy-button',
126 oncopy: D.flashOnce.eventHandler,
127 style: {/*properties copied as-is into element.style*/}
128 };
129
130 })(window.fossil);
131
--- src/fossil.copybutton.js
+++ src/fossil.copybutton.js
@@ -42,13 +42,11 @@
42
43 .oncopy: an optional callback function which is added as an event
44 listener for the 'text-copied' event (see below). There is
45 functionally no difference from setting this option or adding a
46 'text-copied' event listener to the element, and this option is
47 considered to be a convenience form of that.
 
 
48
49 Note that this function's own defaultOptions object holds default
50 values for some options. Any changes made to that object affect
51 any future calls to this function.
52
@@ -62,25 +60,21 @@
60 member is an object with a "text" property holding the copied
61 text. Other properties may be added in the future. The event is
62 not fired if copying to the clipboard fails (e.g. is not
63 available in the current environment).
64
65 The copy button's click handler is suppressed (becomes a no-op)
66 for as long as the element has the "disabled" attribute.
 
 
 
67
68 Returns the copy-initialized element.
69
70 Example:
71
72 const button = fossil.copyButton('#my-copy-button', {
73 copyFromId: 'some-other-element-id'
74 });
75 button.addEventListener('text-copied',function(ev){
 
76 console.debug("Copied text:",ev.detail.text);
77 });
78 */
79 F.copyButton = function f(e, opt){
80 if('string'===typeof e){
@@ -103,11 +97,11 @@
97 e.addEventListener(
98 'click',
99 function(ev){
100 ev.preventDefault();
101 ev.stopPropagation();
102 if(e.disabled) return; /* This check is probably redundant. */
103 const txt = extract.call(opt);
104 if(txt && D.copyTextToClipboard(txt)){
105 e.dispatchEvent(new CustomEvent('text-copied',{
106 detail: {text: txt}
107 }));
@@ -116,15 +110,19 @@
110 false
111 );
112 if('function' === typeof opt.oncopy){
113 e.addEventListener('text-copied', opt.oncopy, false);
114 }
115 /* Make sure the <button> contains a single nested <span>. */
116 if(e.childElementCount!=1 || e.firstChild.tagName!='SPAN'){
117 D.append(D.clearElement(e), D.span());
118 }
119 return e;
120 };
121
122 F.copyButton.defaultOptions = {
123 cssClass: 'copy-button',
124 oncopy: undefined,
125 style: {/*properties copied as-is into element.style*/}
126 };
127
128 })(window.fossil);
129
--- src/fossil.numbered-lines.js
+++ src/fossil.numbered-lines.js
@@ -23,13 +23,10 @@
2323
.replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */
2424
.replace(/&?\bln=[^&]*/,'') /* inbound line number/range */
2525
.replace('?&','?');
2626
const lineState = { urlArgs: urlArgsRaw, start: 0, end: 0 };
2727
const lineTip = new F.PopupWidget({
28
- style: {
29
- cursor: 'pointer'
30
- },
3128
refresh: function(){
3229
const link = this.state.link;
3330
D.clearElement(link);
3431
if(lineState.start){
3532
const ls = [lineState.start];
@@ -48,23 +45,22 @@
4845
D.append(link, "No lines selected.");
4946
}
5047
},
5148
init: function(){
5249
const e = this.e;
53
- const btnCopy = D.span(),
54
- link = D.span();
50
+ const btnCopy = D.attr(D.button(), 'id', 'linenum-copy-button');
51
+ link = D.label('linenum-copy-button');
5552
this.state = {link};
5653
F.copyButton(btnCopy,{
5754
copyFromElement: link,
5855
extractText: ()=>link.dataset.url,
5956
oncopy: (ev)=>{
60
- D.flashOnce(ev.target, undefined, ()=>lineTip.hide());
57
+ setTimeout(()=>lineTip.hide(), 400);
6158
// arguably too snazzy: F.toast.message("Copied link to clipboard.");
6259
}
6360
});
64
- this.e.addEventListener('click', ()=>btnCopy.click(), false);
65
- D.append(this.e, btnCopy, link)
61
+ D.append(this.e, btnCopy, link);
6662
}
6763
});
6864
6965
tbl.addEventListener('click', ()=>lineTip.hide(), true);
7066
7167
--- src/fossil.numbered-lines.js
+++ src/fossil.numbered-lines.js
@@ -23,13 +23,10 @@
23 .replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */
24 .replace(/&?\bln=[^&]*/,'') /* inbound line number/range */
25 .replace('?&','?');
26 const lineState = { urlArgs: urlArgsRaw, start: 0, end: 0 };
27 const lineTip = new F.PopupWidget({
28 style: {
29 cursor: 'pointer'
30 },
31 refresh: function(){
32 const link = this.state.link;
33 D.clearElement(link);
34 if(lineState.start){
35 const ls = [lineState.start];
@@ -48,23 +45,22 @@
48 D.append(link, "No lines selected.");
49 }
50 },
51 init: function(){
52 const e = this.e;
53 const btnCopy = D.span(),
54 link = D.span();
55 this.state = {link};
56 F.copyButton(btnCopy,{
57 copyFromElement: link,
58 extractText: ()=>link.dataset.url,
59 oncopy: (ev)=>{
60 D.flashOnce(ev.target, undefined, ()=>lineTip.hide());
61 // arguably too snazzy: F.toast.message("Copied link to clipboard.");
62 }
63 });
64 this.e.addEventListener('click', ()=>btnCopy.click(), false);
65 D.append(this.e, btnCopy, link)
66 }
67 });
68
69 tbl.addEventListener('click', ()=>lineTip.hide(), true);
70
71
--- src/fossil.numbered-lines.js
+++ src/fossil.numbered-lines.js
@@ -23,13 +23,10 @@
23 .replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */
24 .replace(/&?\bln=[^&]*/,'') /* inbound line number/range */
25 .replace('?&','?');
26 const lineState = { urlArgs: urlArgsRaw, start: 0, end: 0 };
27 const lineTip = new F.PopupWidget({
 
 
 
28 refresh: function(){
29 const link = this.state.link;
30 D.clearElement(link);
31 if(lineState.start){
32 const ls = [lineState.start];
@@ -48,23 +45,22 @@
45 D.append(link, "No lines selected.");
46 }
47 },
48 init: function(){
49 const e = this.e;
50 const btnCopy = D.attr(D.button(), 'id', 'linenum-copy-button');
51 link = D.label('linenum-copy-button');
52 this.state = {link};
53 F.copyButton(btnCopy,{
54 copyFromElement: link,
55 extractText: ()=>link.dataset.url,
56 oncopy: (ev)=>{
57 setTimeout(()=>lineTip.hide(), 400);
58 // arguably too snazzy: F.toast.message("Copied link to clipboard.");
59 }
60 });
61 D.append(this.e, btnCopy, link);
 
62 }
63 });
64
65 tbl.addEventListener('click', ()=>lineTip.hide(), true);
66
67
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -901,14 +901,13 @@
901901
const cpId = 'copy-to-clipboard-'+id;
902902
/* ^^^ copy button element ID, needed for LABEL element
903903
pairing. Recall that we destroy all child elements of
904904
`content` each time we hit this block, so we can reuse
905905
that element ID on subsequent toggles. */
906
- const btnCp = D.attr(D.addClass(D.span(),'copy-button'), 'id', cpId);
906
+ const btnCp = D.attr(D.addClass(D.button(),'copy-button'), 'id', cpId);
907907
F.copyButton(btnCp, {extractText: ()=>child._xmsgRaw});
908908
const lblCp = D.label(cpId, "Copy unformatted text");
909
- lblCp.addEventListener('click',()=>btnCp.click(), false);
910909
D.append(content, D.append(D.addClass(D.span(), 'nobr'), btnCp, lblCp));
911910
}
912911
delete e.$isToggling;
913912
D.append(content, child);
914913
return;
915914
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -901,14 +901,13 @@
901 const cpId = 'copy-to-clipboard-'+id;
902 /* ^^^ copy button element ID, needed for LABEL element
903 pairing. Recall that we destroy all child elements of
904 `content` each time we hit this block, so we can reuse
905 that element ID on subsequent toggles. */
906 const btnCp = D.attr(D.addClass(D.span(),'copy-button'), 'id', cpId);
907 F.copyButton(btnCp, {extractText: ()=>child._xmsgRaw});
908 const lblCp = D.label(cpId, "Copy unformatted text");
909 lblCp.addEventListener('click',()=>btnCp.click(), false);
910 D.append(content, D.append(D.addClass(D.span(), 'nobr'), btnCp, lblCp));
911 }
912 delete e.$isToggling;
913 D.append(content, child);
914 return;
915
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -901,14 +901,13 @@
901 const cpId = 'copy-to-clipboard-'+id;
902 /* ^^^ copy button element ID, needed for LABEL element
903 pairing. Recall that we destroy all child elements of
904 `content` each time we hit this block, so we can reuse
905 that element ID on subsequent toggles. */
906 const btnCp = D.attr(D.addClass(D.button(),'copy-button'), 'id', cpId);
907 F.copyButton(btnCp, {extractText: ()=>child._xmsgRaw});
908 const lblCp = D.label(cpId, "Copy unformatted text");
 
909 D.append(content, D.append(D.addClass(D.span(), 'nobr'), btnCp, lblCp));
910 }
911 delete e.$isToggling;
912 D.append(content, child);
913 return;
914
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -47,11 +47,11 @@
4747
document.body.classList.add('pikchrshow');
4848
P.e = { /* various DOM elements we work with... */
4949
previewTarget: E('#pikchrshow-output'),
5050
previewLegend: E('#pikchrshow-output-wrapper > legend'),
5151
previewCopyButton: D.attr(
52
- D.addClass(D.span(),'copy-button'),
52
+ D.addClass(D.button(),'copy-button'),
5353
'id','preview-copy-button'
5454
),
5555
previewModeLabel: D.label('preview-copy-button'),
5656
btnSubmit: E('#pikchr-submit-preview'),
5757
btnStash: E('#pikchr-stash'),
@@ -119,11 +119,10 @@
119119
}, false);
120120
121121
////////////////////////////////////////////////////////////
122122
// Setup clipboard-copy of markup/SVG...
123123
F.copyButton(P.e.previewCopyButton, {copyFromElement: P.e.taPreviewText});
124
- P.e.previewModeLabel.addEventListener('click', ()=>P.e.previewCopyButton.click(), false);
125124
126125
////////////////////////////////////////////////////////////
127126
// Set up dark mode simulator...
128127
P.e.cbDarkMode.addEventListener('change', function(ev){
129128
if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode');
@@ -348,11 +347,11 @@
348347
D.addClass(preTgt, 'error');
349348
this.e.previewModeLabel.innerText = "Error";
350349
return;
351350
}
352351
D.removeClass(preTgt, 'error');
353
- D.removeClass(this.e.previewCopyButton, 'disabled');
352
+ this.e.previewCopyButton.disabled = false;
354353
D.removeClass(this.e.markupAlignWrapper, 'hidden');
355354
D.enable(this.e.previewModeToggle, this.e.markupAlignRadios);
356355
let label, svg;
357356
switch(this.previewMode){
358357
case 0:
@@ -427,11 +426,11 @@
427426
P.renderPreview();
428427
};
429428
}
430429
D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios);
431430
D.addClass(this.e.markupAlignWrapper, 'hidden');
432
- D.addClass(this.e.previewCopyButton, 'disabled');
431
+ this.e.previewCopyButton.disabled = true;
433432
const content = this.e.taContent.value.trim();
434433
this.response.raw = this.response.rawSvg = undefined;
435434
this.response.inputText = content;
436435
const sampleScript = fp.$_sampleScript;
437436
delete fp.$_sampleScript;
438437
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -47,11 +47,11 @@
47 document.body.classList.add('pikchrshow');
48 P.e = { /* various DOM elements we work with... */
49 previewTarget: E('#pikchrshow-output'),
50 previewLegend: E('#pikchrshow-output-wrapper > legend'),
51 previewCopyButton: D.attr(
52 D.addClass(D.span(),'copy-button'),
53 'id','preview-copy-button'
54 ),
55 previewModeLabel: D.label('preview-copy-button'),
56 btnSubmit: E('#pikchr-submit-preview'),
57 btnStash: E('#pikchr-stash'),
@@ -119,11 +119,10 @@
119 }, false);
120
121 ////////////////////////////////////////////////////////////
122 // Setup clipboard-copy of markup/SVG...
123 F.copyButton(P.e.previewCopyButton, {copyFromElement: P.e.taPreviewText});
124 P.e.previewModeLabel.addEventListener('click', ()=>P.e.previewCopyButton.click(), false);
125
126 ////////////////////////////////////////////////////////////
127 // Set up dark mode simulator...
128 P.e.cbDarkMode.addEventListener('change', function(ev){
129 if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode');
@@ -348,11 +347,11 @@
348 D.addClass(preTgt, 'error');
349 this.e.previewModeLabel.innerText = "Error";
350 return;
351 }
352 D.removeClass(preTgt, 'error');
353 D.removeClass(this.e.previewCopyButton, 'disabled');
354 D.removeClass(this.e.markupAlignWrapper, 'hidden');
355 D.enable(this.e.previewModeToggle, this.e.markupAlignRadios);
356 let label, svg;
357 switch(this.previewMode){
358 case 0:
@@ -427,11 +426,11 @@
427 P.renderPreview();
428 };
429 }
430 D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios);
431 D.addClass(this.e.markupAlignWrapper, 'hidden');
432 D.addClass(this.e.previewCopyButton, 'disabled');
433 const content = this.e.taContent.value.trim();
434 this.response.raw = this.response.rawSvg = undefined;
435 this.response.inputText = content;
436 const sampleScript = fp.$_sampleScript;
437 delete fp.$_sampleScript;
438
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -47,11 +47,11 @@
47 document.body.classList.add('pikchrshow');
48 P.e = { /* various DOM elements we work with... */
49 previewTarget: E('#pikchrshow-output'),
50 previewLegend: E('#pikchrshow-output-wrapper > legend'),
51 previewCopyButton: D.attr(
52 D.addClass(D.button(),'copy-button'),
53 'id','preview-copy-button'
54 ),
55 previewModeLabel: D.label('preview-copy-button'),
56 btnSubmit: E('#pikchr-submit-preview'),
57 btnStash: E('#pikchr-stash'),
@@ -119,11 +119,10 @@
119 }, false);
120
121 ////////////////////////////////////////////////////////////
122 // Setup clipboard-copy of markup/SVG...
123 F.copyButton(P.e.previewCopyButton, {copyFromElement: P.e.taPreviewText});
 
124
125 ////////////////////////////////////////////////////////////
126 // Set up dark mode simulator...
127 P.e.cbDarkMode.addEventListener('change', function(ev){
128 if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode');
@@ -348,11 +347,11 @@
347 D.addClass(preTgt, 'error');
348 this.e.previewModeLabel.innerText = "Error";
349 return;
350 }
351 D.removeClass(preTgt, 'error');
352 this.e.previewCopyButton.disabled = false;
353 D.removeClass(this.e.markupAlignWrapper, 'hidden');
354 D.enable(this.e.previewModeToggle, this.e.markupAlignRadios);
355 let label, svg;
356 switch(this.previewMode){
357 case 0:
@@ -427,11 +426,11 @@
426 P.renderPreview();
427 };
428 }
429 D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios);
430 D.addClass(this.e.markupAlignWrapper, 'hidden');
431 this.e.previewCopyButton.disabled = true;
432 const content = this.e.taContent.value.trim();
433 this.response.raw = this.response.rawSvg = undefined;
434 this.response.inputText = content;
435 const sampleScript = fp.$_sampleScript;
436 delete fp.$_sampleScript;
437
--- src/fossil.page.pikchrshowasm.js
+++ src/fossil.page.pikchrshowasm.js
@@ -312,11 +312,10 @@
312312
if(this.e.pikOut.dataset.pikchr){
313313
this.render(this.e.pikOut.dataset.pikchr);
314314
}
315315
}.bind(PS));
316316
F.copyButton(PS.e.previewCopyButton, {copyFromElement: PS.e.outText});
317
- PS.e.previewModeLabel.addEventListener('click', ()=>PS.e.previewCopyButton.click(), false);
318317
319318
PS.addMsgHandler('working',function f(ev){
320319
switch(ev.data){
321320
case 'start': /* See notes in preStartWork(). */; return;
322321
case 'end':
323322
--- src/fossil.page.pikchrshowasm.js
+++ src/fossil.page.pikchrshowasm.js
@@ -312,11 +312,10 @@
312 if(this.e.pikOut.dataset.pikchr){
313 this.render(this.e.pikOut.dataset.pikchr);
314 }
315 }.bind(PS));
316 F.copyButton(PS.e.previewCopyButton, {copyFromElement: PS.e.outText});
317 PS.e.previewModeLabel.addEventListener('click', ()=>PS.e.previewCopyButton.click(), false);
318
319 PS.addMsgHandler('working',function f(ev){
320 switch(ev.data){
321 case 'start': /* See notes in preStartWork(). */; return;
322 case 'end':
323
--- src/fossil.page.pikchrshowasm.js
+++ src/fossil.page.pikchrshowasm.js
@@ -312,11 +312,10 @@
312 if(this.e.pikOut.dataset.pikchr){
313 this.render(this.e.pikOut.dataset.pikchr);
314 }
315 }.bind(PS));
316 F.copyButton(PS.e.previewCopyButton, {copyFromElement: PS.e.outText});
 
317
318 PS.addMsgHandler('working',function f(ev){
319 switch(ev.data){
320 case 'start': /* See notes in preStartWork(). */; return;
321 case 'end':
322
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -1234,13 +1234,13 @@
12341234
encodeURIComponent(a.filename)
12351235
].join(''),
12361236
"raw/"+a.src
12371237
].forEach(function(url){
12381238
const imgUrl = D.append(D.addClass(D.span(), 'monospace'), url);
1239
- const urlCopy = D.span();
1239
+ const urlCopy = D.button();
12401240
const li = D.li(ul);
1241
- D.append(li, urlCopy, " ", imgUrl);
1241
+ D.append(li, urlCopy, imgUrl);
12421242
F.copyButton(urlCopy, {copyFromElement: imgUrl});
12431243
});
12441244
});
12451245
return this;
12461246
};
12471247
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -1234,13 +1234,13 @@
1234 encodeURIComponent(a.filename)
1235 ].join(''),
1236 "raw/"+a.src
1237 ].forEach(function(url){
1238 const imgUrl = D.append(D.addClass(D.span(), 'monospace'), url);
1239 const urlCopy = D.span();
1240 const li = D.li(ul);
1241 D.append(li, urlCopy, " ", imgUrl);
1242 F.copyButton(urlCopy, {copyFromElement: imgUrl});
1243 });
1244 });
1245 return this;
1246 };
1247
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -1234,13 +1234,13 @@
1234 encodeURIComponent(a.filename)
1235 ].join(''),
1236 "raw/"+a.src
1237 ].forEach(function(url){
1238 const imgUrl = D.append(D.addClass(D.span(), 'monospace'), url);
1239 const urlCopy = D.button();
1240 const li = D.li(ul);
1241 D.append(li, urlCopy, imgUrl);
1242 F.copyButton(urlCopy, {copyFromElement: imgUrl});
1243 });
1244 });
1245 return this;
1246 };
1247
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -462,12 +462,12 @@
462462
} CX("</fieldset><!-- .zone-wrapper.input -->");
463463
CX("<fieldset class='zone-wrapper output'>"); {
464464
CX("<legend><div class='button-bar'>");
465465
CX("<button id='btn-render-mode'>Render Mode</button> ");
466466
CX("<span style='white-space:nowrap'>"
467
- "<span id='preview-copy-button' "
468
- "title='Tap to copy to clipboard.'></span>"
467
+ "<button id='preview-copy-button' "
468
+ "title='Tap to copy to clipboard.'><span></span></button>"
469469
"<label for='preview-copy-button' "
470470
"title='Tap to copy to clipboard.'></label>"
471471
"</span>");
472472
CX("</div></legend>");
473473
CX("<div id='pikchr-output-wrapper'>");
474474
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -462,12 +462,12 @@
462 } CX("</fieldset><!-- .zone-wrapper.input -->");
463 CX("<fieldset class='zone-wrapper output'>"); {
464 CX("<legend><div class='button-bar'>");
465 CX("<button id='btn-render-mode'>Render Mode</button> ");
466 CX("<span style='white-space:nowrap'>"
467 "<span id='preview-copy-button' "
468 "title='Tap to copy to clipboard.'></span>"
469 "<label for='preview-copy-button' "
470 "title='Tap to copy to clipboard.'></label>"
471 "</span>");
472 CX("</div></legend>");
473 CX("<div id='pikchr-output-wrapper'>");
474
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -462,12 +462,12 @@
462 } CX("</fieldset><!-- .zone-wrapper.input -->");
463 CX("<fieldset class='zone-wrapper output'>"); {
464 CX("<legend><div class='button-bar'>");
465 CX("<button id='btn-render-mode'>Render Mode</button> ");
466 CX("<span style='white-space:nowrap'>"
467 "<button id='preview-copy-button' "
468 "title='Tap to copy to clipboard.'><span></span></button>"
469 "<label for='preview-copy-button' "
470 "title='Tap to copy to clipboard.'></label>"
471 "</span>");
472 CX("</div></legend>");
473 CX("<div id='pikchr-output-wrapper'>");
474
+20 -16
--- src/style.c
+++ src/style.c
@@ -478,11 +478,11 @@
478478
/*
479479
** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
480480
** Javascript module, and generates HTML elements with the following IDs:
481481
**
482482
** TARGETID: The <span> wrapper around TEXT.
483
-** copy-TARGETID: The <span> for the copy button.
483
+** copy-TARGETID: The <button> for the copy button.
484484
**
485485
** If the FLIPPED argument is non-zero, the copy button is displayed after TEXT.
486486
**
487487
** The COPYLENGTH argument defines the length of the substring of TEXT copied to
488488
** clipboard:
@@ -510,18 +510,20 @@
510510
if( cchLength==1 ) cchLength = hash_digits(0);
511511
else if( cchLength==2 ) cchLength = hash_digits(1);
512512
if( !bFlipped ){
513513
const char *zBtnFmt =
514514
"<span class=\"nobr\">"
515
- "<span "
516
- "class=\"copy-button\" "
517
- "id=\"copy-%h\" "
518
- "data-copytarget=\"%h\" "
519
- "data-copylength=\"%d\">"
520
- "</span>"
515
+ "<button "
516
+ "class=\"copy-button\" "
517
+ "id=\"copy-%h\" "
518
+ "data-copytarget=\"%h\" "
519
+ "data-copylength=\"%d\">"
520
+ "<span>"
521
+ "</span>"
522
+ "</button>"
521523
"<span id=\"%h\">"
522
- "%s"
524
+ "%s"
523525
"</span>"
524526
"</span>";
525527
if( bOutputCGI ){
526528
cgi_printf(
527529
zBtnFmt/*works-like:"%h%h%d%h%s"*/,
@@ -533,18 +535,20 @@
533535
}
534536
}else{
535537
const char *zBtnFmt =
536538
"<span class=\"nobr\">"
537539
"<span id=\"%h\">"
538
- "%s"
539
- "</span>"
540
- "<span "
541
- "class=\"copy-button copy-button-flipped\" "
542
- "id=\"copy-%h\" "
543
- "data-copytarget=\"%h\" "
544
- "data-copylength=\"%d\">"
545
- "</span>"
540
+ "%s"
541
+ "</span>"
542
+ "<button "
543
+ "class=\"copy-button copy-button-flipped\" "
544
+ "id=\"copy-%h\" "
545
+ "data-copytarget=\"%h\" "
546
+ "data-copylength=\"%d\">"
547
+ "<span>"
548
+ "</span>"
549
+ "</button>"
546550
"</span>";
547551
if( bOutputCGI ){
548552
cgi_printf(
549553
zBtnFmt/*works-like:"%h%s%h%h%d"*/,
550554
zTargetId,zText,zTargetId,zTargetId,cchLength);
551555
--- src/style.c
+++ src/style.c
@@ -478,11 +478,11 @@
478 /*
479 ** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
480 ** Javascript module, and generates HTML elements with the following IDs:
481 **
482 ** TARGETID: The <span> wrapper around TEXT.
483 ** copy-TARGETID: The <span> for the copy button.
484 **
485 ** If the FLIPPED argument is non-zero, the copy button is displayed after TEXT.
486 **
487 ** The COPYLENGTH argument defines the length of the substring of TEXT copied to
488 ** clipboard:
@@ -510,18 +510,20 @@
510 if( cchLength==1 ) cchLength = hash_digits(0);
511 else if( cchLength==2 ) cchLength = hash_digits(1);
512 if( !bFlipped ){
513 const char *zBtnFmt =
514 "<span class=\"nobr\">"
515 "<span "
516 "class=\"copy-button\" "
517 "id=\"copy-%h\" "
518 "data-copytarget=\"%h\" "
519 "data-copylength=\"%d\">"
520 "</span>"
 
 
521 "<span id=\"%h\">"
522 "%s"
523 "</span>"
524 "</span>";
525 if( bOutputCGI ){
526 cgi_printf(
527 zBtnFmt/*works-like:"%h%h%d%h%s"*/,
@@ -533,18 +535,20 @@
533 }
534 }else{
535 const char *zBtnFmt =
536 "<span class=\"nobr\">"
537 "<span id=\"%h\">"
538 "%s"
539 "</span>"
540 "<span "
541 "class=\"copy-button copy-button-flipped\" "
542 "id=\"copy-%h\" "
543 "data-copytarget=\"%h\" "
544 "data-copylength=\"%d\">"
545 "</span>"
 
 
546 "</span>";
547 if( bOutputCGI ){
548 cgi_printf(
549 zBtnFmt/*works-like:"%h%s%h%h%d"*/,
550 zTargetId,zText,zTargetId,zTargetId,cchLength);
551
--- src/style.c
+++ src/style.c
@@ -478,11 +478,11 @@
478 /*
479 ** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
480 ** Javascript module, and generates HTML elements with the following IDs:
481 **
482 ** TARGETID: The <span> wrapper around TEXT.
483 ** copy-TARGETID: The <button> for the copy button.
484 **
485 ** If the FLIPPED argument is non-zero, the copy button is displayed after TEXT.
486 **
487 ** The COPYLENGTH argument defines the length of the substring of TEXT copied to
488 ** clipboard:
@@ -510,18 +510,20 @@
510 if( cchLength==1 ) cchLength = hash_digits(0);
511 else if( cchLength==2 ) cchLength = hash_digits(1);
512 if( !bFlipped ){
513 const char *zBtnFmt =
514 "<span class=\"nobr\">"
515 "<button "
516 "class=\"copy-button\" "
517 "id=\"copy-%h\" "
518 "data-copytarget=\"%h\" "
519 "data-copylength=\"%d\">"
520 "<span>"
521 "</span>"
522 "</button>"
523 "<span id=\"%h\">"
524 "%s"
525 "</span>"
526 "</span>";
527 if( bOutputCGI ){
528 cgi_printf(
529 zBtnFmt/*works-like:"%h%h%d%h%s"*/,
@@ -533,18 +535,20 @@
535 }
536 }else{
537 const char *zBtnFmt =
538 "<span class=\"nobr\">"
539 "<span id=\"%h\">"
540 "%s"
541 "</span>"
542 "<button "
543 "class=\"copy-button copy-button-flipped\" "
544 "id=\"copy-%h\" "
545 "data-copytarget=\"%h\" "
546 "data-copylength=\"%d\">"
547 "<span>"
548 "</span>"
549 "</button>"
550 "</span>";
551 if( bOutputCGI ){
552 cgi_printf(
553 zBtnFmt/*works-like:"%h%s%h%h%d"*/,
554 zTargetId,zText,zTargetId,zTargetId,cchLength);
555

Keyboard Shortcuts

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