feedtack 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/feedtack.inject.js +199 -0
- package/package.json +1 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";(()=>{var P="2.0.0";function v(){return{width:window.innerWidth,height:window.innerHeight,scrollX:window.scrollX,scrollY:window.scrollY,devicePixelRatio:window.devicePixelRatio}}function w(){return{url:window.location.href,pathname:window.location.pathname,title:document.title}}function E(){return{userAgent:navigator.userAgent,platform:navigator.platform,touchEnabled:navigator.maxTouchPoints>0}}function C(n){let t=n.clientX+window.scrollX,e=n.clientY+window.scrollY,o=document.documentElement.scrollWidth,a=document.documentElement.scrollHeight;return{x:t,y:e,xPct:Number((t/o*100).toFixed(2)),yPct:Number((e/a*100).toFixed(2))}}var A="button,a,input,select,textarea,label";function S(n){return n.closest(A)??n}function f(n,t){return n.getAttribute(t)}function U(n){let t=1,e=n.previousElementSibling;for(;e;)t++,e=e.previousElementSibling;return t}function B(n){let t=n.tagName,e=1,o=n.previousElementSibling;for(;o;)o.tagName===t&&e++,o=o.previousElementSibling;return e}function R(n){let t=f(n,"id"),e=f(n,"data-testid")??f(n,"data-test-id"),o=f(n,"data-feedtack-component"),a=!!(t||e);return{tag:n.tagName.toLowerCase(),id:t,ariaLabel:f(n,"aria-label"),role:f(n,"role"),type:f(n,"type"),name:f(n,"name"),title:f(n,"title"),alt:f(n,"alt"),dataTestId:e,dataFeedtackComponent:o,nthChild:a?null:U(n),nthOfType:a?null:B(n),componentName:o??null}}function _(n){let t=[],e=n.parentElement;for(;e&&e!==document.body&&t.length<5;)t.push(R(e)),e=e.parentElement;return t}function $(n){let t=[],e=n;for(;e&&e!==document.body;){let o=e.getAttribute("id"),a=e.getAttribute("data-testid")??e.getAttribute("data-test-id"),r=e.getAttribute("data-feedtack-component");if(o){t.unshift(`#${o}`);break}else if(a){t.unshift(`[data-testid="${a}"]`);break}else if(r){t.unshift(`[data-feedtack-component="${r}"]`);break}else{let s=e.tagName.toLowerCase(),p=e.parentElement;if(p){let d=Array.from(p.children).filter(g=>g.tagName===e.tagName),m=d.indexOf(e)+1;t.unshift(d.length>1?`${s}:nth-of-type(${m})`:s)}else t.unshift(s)}e=e.parentElement}return t.join(" > ")}function D(n,t){if(n.getAttribute("data-testid")??n.getAttribute("data-test-id"))return null;let o=(()=>{let r=n.tagName.toLowerCase(),s=Array.from(n.classList).join(".");return s?`${r}.${s}`:r})(),a=t.map(r=>r.dataTestId?`[data-testid="${r.dataTestId}"]`:r.tag);return[o,...a].join(" > ")}function F(n){let t=S(n),e=t.getAttribute("id"),o=t.getAttribute("data-testid")??t.getAttribute("data-test-id"),a=t.getAttribute("data-feedtack-component"),r,s;e?(r=`#${e}`,s=!1):o?(r=`[data-testid="${o}"]`,s=!1):a?(r=`[data-feedtack-component="${a}"]`,s=!1):(r=$(t),s=!0);let p=_(t),d=t.getBoundingClientRect();return{selector:r,best_effort:s,dataTestId:o,elementPath:D(t,p),tagName:t.tagName,ancestors:p,boundingRect:{x:d.x+window.scrollX,y:d.y+window.scrollY,width:d.width,height:d.height}}}var y={id:"anon",name:"Anonymous",role:"reviewer"};var H="1.2.0";function z(n){let t=new URL(n);if(t.protocol!=="https:")throw new Error(`[feedtack] Webhook URL must use https: protocol, got ${t.protocol}`);return t.href}function M(n){let t={version:H,user:y};return n&&(n.webhookUrl&&(t.webhookUrl=z(n.webhookUrl)),n.user&&(t.user={id:n.user.id??y.id,name:n.user.name??y.name,role:n.user.role??y.role,...n.user.avatarUrl?{avatarUrl:n.user.avatarUrl}:{},...n.user.email?{email:n.user.email}:{}})),t}async function I(n,t){let e=JSON.stringify(n,null,2);if(t.webhookUrl){let o=new Blob([e],{type:"application/json"});if(!navigator.sendBeacon(t.webhookUrl,o))throw new Error("[feedtack] sendBeacon failed \u2014 payload may be too large");return"webhook"}return await navigator.clipboard.writeText(e),"clipboard"}var T=`
|
|
2
|
+
:host {
|
|
3
|
+
all: initial;
|
|
4
|
+
direction: ltr;
|
|
5
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
6
|
+
line-height: 1.5;
|
|
7
|
+
color: #111827;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
* {
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.ft-fab {
|
|
17
|
+
position: fixed;
|
|
18
|
+
bottom: 24px;
|
|
19
|
+
right: 24px;
|
|
20
|
+
z-index: 2147483640;
|
|
21
|
+
background: #111827;
|
|
22
|
+
color: #fff;
|
|
23
|
+
border: none;
|
|
24
|
+
border-radius: 8px;
|
|
25
|
+
padding: 10px 16px;
|
|
26
|
+
font-size: 13px;
|
|
27
|
+
font-weight: 500;
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
|
|
30
|
+
min-width: 44px;
|
|
31
|
+
min-height: 44px;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: 6px;
|
|
35
|
+
transition: background 0.15s;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.ft-fab:hover { opacity: 0.85; }
|
|
39
|
+
.ft-fab.active { background: #2563eb; }
|
|
40
|
+
|
|
41
|
+
.ft-panel {
|
|
42
|
+
position: fixed;
|
|
43
|
+
bottom: 80px;
|
|
44
|
+
right: 24px;
|
|
45
|
+
z-index: 2147483641;
|
|
46
|
+
width: 320px;
|
|
47
|
+
max-height: 80vh;
|
|
48
|
+
background: #fff;
|
|
49
|
+
border: 1px solid #e5e7eb;
|
|
50
|
+
border-radius: 12px;
|
|
51
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.18);
|
|
52
|
+
display: none;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.ft-panel.open { display: flex; }
|
|
58
|
+
|
|
59
|
+
.ft-panel-header {
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
justify-content: space-between;
|
|
63
|
+
padding: 14px 16px;
|
|
64
|
+
border-bottom: 1px solid #e5e7eb;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.ft-panel-title {
|
|
68
|
+
font-size: 15px;
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.ft-panel-close {
|
|
73
|
+
background: none;
|
|
74
|
+
border: none;
|
|
75
|
+
font-size: 20px;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
color: #6b7280;
|
|
78
|
+
padding: 0 4px;
|
|
79
|
+
min-width: 44px;
|
|
80
|
+
min-height: 44px;
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.ft-panel-body {
|
|
87
|
+
padding: 16px;
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
gap: 12px;
|
|
91
|
+
overflow-y: auto;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.ft-textarea {
|
|
95
|
+
width: 100%;
|
|
96
|
+
border: 1.5px solid #e5e7eb;
|
|
97
|
+
border-radius: 8px;
|
|
98
|
+
padding: 8px;
|
|
99
|
+
font-size: 13px;
|
|
100
|
+
font-family: inherit;
|
|
101
|
+
resize: vertical;
|
|
102
|
+
min-height: 80px;
|
|
103
|
+
outline: none;
|
|
104
|
+
background: #f9fafb;
|
|
105
|
+
color: #111827;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.ft-textarea:focus { border-color: #2563eb; }
|
|
109
|
+
.ft-textarea.error { border-color: #ef4444; }
|
|
110
|
+
|
|
111
|
+
.ft-sentiment {
|
|
112
|
+
display: flex;
|
|
113
|
+
gap: 8px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.ft-sentiment button {
|
|
117
|
+
flex: 1;
|
|
118
|
+
padding: 6px 10px;
|
|
119
|
+
border: 1.5px solid #e5e7eb;
|
|
120
|
+
border-radius: 8px;
|
|
121
|
+
background: #fff;
|
|
122
|
+
color: #111827;
|
|
123
|
+
font-size: 12px;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
min-height: 44px;
|
|
126
|
+
transition: all 0.1s;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.ft-sentiment button.selected {
|
|
130
|
+
border-color: #2563eb;
|
|
131
|
+
background: #f9fafb;
|
|
132
|
+
color: #2563eb;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.ft-actions {
|
|
136
|
+
display: flex;
|
|
137
|
+
gap: 8px;
|
|
138
|
+
justify-content: flex-end;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.ft-btn-cancel {
|
|
142
|
+
padding: 8px 14px;
|
|
143
|
+
border: 1.5px solid #e5e7eb;
|
|
144
|
+
border-radius: 8px;
|
|
145
|
+
background: #fff;
|
|
146
|
+
color: #111827;
|
|
147
|
+
font-size: 13px;
|
|
148
|
+
cursor: pointer;
|
|
149
|
+
min-height: 44px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.ft-btn-submit {
|
|
153
|
+
padding: 8px 14px;
|
|
154
|
+
border: none;
|
|
155
|
+
border-radius: 8px;
|
|
156
|
+
background: #2563eb;
|
|
157
|
+
color: #fff;
|
|
158
|
+
font-size: 13px;
|
|
159
|
+
font-weight: 500;
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
min-height: 44px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.ft-btn-submit:disabled {
|
|
165
|
+
opacity: 0.5;
|
|
166
|
+
cursor: not-allowed;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.ft-pin-count {
|
|
170
|
+
font-size: 12px;
|
|
171
|
+
color: #6b7280;
|
|
172
|
+
padding: 4px 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.ft-status {
|
|
176
|
+
font-size: 12px;
|
|
177
|
+
color: #22c55e;
|
|
178
|
+
padding: 4px 0;
|
|
179
|
+
text-align: center;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.ft-error-msg {
|
|
183
|
+
font-size: 12px;
|
|
184
|
+
color: #ef4444;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@media (max-width: 480px) {
|
|
188
|
+
.ft-panel {
|
|
189
|
+
right: 0;
|
|
190
|
+
bottom: 72px;
|
|
191
|
+
width: 100vw;
|
|
192
|
+
max-height: 80vh;
|
|
193
|
+
border-radius: 12px 12px 0 0;
|
|
194
|
+
border-left: none;
|
|
195
|
+
border-right: none;
|
|
196
|
+
border-bottom: none;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
`;function N(){let n=document.createElement("div");n.id="feedtack-inject",document.body.appendChild(n);let t=n.attachShadow({mode:"closed"}),e=document.createElement("style");e.textContent=T,t.appendChild(e);let o=document.createElement("button");o.className="ft-fab",o.textContent="Feedback",o.setAttribute("aria-label","Toggle feedtack feedback panel"),t.appendChild(o);let a=document.createElement("div");a.className="ft-panel",a.setAttribute("role","dialog"),a.setAttribute("aria-label","Feedtack feedback panel");let r=document.createElement("div");r.className="ft-panel-header";let s=document.createElement("span");s.className="ft-panel-title",s.textContent="Feedback";let p=document.createElement("button");p.className="ft-panel-close",p.textContent="\xD7",p.setAttribute("aria-label","Close panel"),r.appendChild(s),r.appendChild(p),a.appendChild(r);let d=document.createElement("div");d.className="ft-panel-body";let m=document.createElement("div");m.className="ft-pin-count",m.textContent="Click on elements to place pins, then submit.",d.appendChild(m);let g=document.createElement("textarea");g.className="ft-textarea",g.placeholder="Describe your feedback...",g.setAttribute("aria-label","Feedback comment"),d.appendChild(g);let b=document.createElement("div");b.className="ft-error-msg",b.style.display="none",b.textContent="Please enter a comment.",d.appendChild(b);let x=document.createElement("div");x.className="ft-sentiment";let i=document.createElement("button");i.textContent="Good",i.setAttribute("aria-label","Mark as good");let c=document.createElement("button");c.textContent="Bad",c.setAttribute("aria-label","Mark as bad"),x.appendChild(i),x.appendChild(c),d.appendChild(x);let l=document.createElement("div");l.className="ft-actions";let u=document.createElement("button");u.className="ft-btn-cancel",u.textContent="Cancel";let h=document.createElement("button");h.className="ft-btn-submit",h.textContent="Submit",l.appendChild(u),l.appendChild(h),d.appendChild(l);let k=document.createElement("div");return k.className="ft-status",k.style.display="none",d.appendChild(k),a.appendChild(d),t.appendChild(a),{shadow:t,fab:o,panel:a,textarea:g,sentimentGood:i,sentimentBad:c,submitBtn:h,cancelBtn:u,pinCount:m,status:k,errorMsg:b}}var j="#2563eb";function O(n,t,e){let o=document.createElement("div");o.style.cssText=["position:absolute",`left:${n}px`,`top:${t}px`,"z-index:2147483641","width:24px","height:24px","border-radius:50% 50% 50% 0","transform:translate(-50%,-100%) rotate(-45deg)",`background:${j}`,"border:2px solid rgba(255,255,255,0.8)","box-shadow:0 2px 6px rgba(0,0,0,0.3)","pointer-events:none"].join(";");let a=document.createElement("span");return a.style.cssText=["position:absolute","inset:0","display:flex","align-items:center","justify-content:center","transform:rotate(45deg)","font-size:12px","font-weight:700","color:#fff","line-height:1"].join(";"),a.textContent=String(e),o.appendChild(a),document.body.appendChild(o),o}function V(n){for(let t of n)t.el.remove();n.length=0}function L(n,t){n.pinCount.textContent=t===0?"Click on elements to place pins, then submit.":`${t} pin${t>1?"s":""} placed. Click more or submit.`}(function(){if(window.__feedtack_injected){console.warn("[feedtack] Already injected \u2014 skipping.");return}window.__feedtack_injected=!0;let t=M(window.__feedtack),e=N(),o=[],a=!1,r=null,s=!1;function p(i){a=i,e.fab.classList.toggle("active",i),i?document.documentElement.style.cursor="crosshair":document.documentElement.style.cursor=""}function d(){s=!0,e.panel.classList.add("open"),p(!0)}function m(){s=!1,e.panel.classList.remove("open"),p(!1),V(o),e.textarea.value="",r=null,e.sentimentGood.classList.remove("selected"),e.sentimentBad.classList.remove("selected"),e.errorMsg.style.display="none",e.status.style.display="none",L(e,0)}e.fab.addEventListener("click",()=>{s?m():d()}),e.panel.querySelector(".ft-panel-close")?.addEventListener("click",m),e.sentimentGood.addEventListener("click",()=>{r=r==="good"?null:"good",e.sentimentGood.classList.toggle("selected",r==="good"),e.sentimentBad.classList.remove("selected")}),e.sentimentBad.addEventListener("click",()=>{r=r==="bad"?null:"bad",e.sentimentBad.classList.toggle("selected",r==="bad"),e.sentimentGood.classList.remove("selected")}),e.cancelBtn.addEventListener("click",m);function g(i){if(!a||!s)return;let c=i.target;c.closest("#feedtack-inject")||(i.preventDefault(),i.stopPropagation(),x(i.clientX,i.clientY,c))}function b(i){if(!a||!s)return;let c=i.changedTouches[0];if(!c)return;let l=document.elementFromPoint(c.clientX,c.clientY);!l||l.closest("#feedtack-inject")||(i.preventDefault(),x(c.clientX,c.clientY,l))}function x(i,c,l){let u=C({clientX:i,clientY:c}),h={color:j,...u,target:F(l)},k=O(u.x,u.y,o.length+1);o.push({el:k,pin:h}),L(e,o.length)}document.addEventListener("click",g,!0),document.addEventListener("touchend",b,!0),window.addEventListener("keydown",i=>{i.key==="Escape"&&s&&m()}),e.submitBtn.addEventListener("click",async()=>{let i=e.textarea.value.trim();if(!i){e.errorMsg.style.display="block",e.textarea.classList.add("error");return}e.errorMsg.style.display="none",e.textarea.classList.remove("error"),e.submitBtn.disabled=!0;let c={schemaVersion:P,id:crypto.randomUUID(),timestamp:new Date().toISOString(),scope:o.length>0?"element":"page",submittedBy:t.user,comment:i,sentiment:r,pins:o.map((l,u)=>({...l.pin,index:u+1})),page:w(),viewport:v(),device:E()};try{let l=await I(c,t);e.status.textContent=l==="clipboard"?"Copied to clipboard!":"Sent to webhook!",e.status.style.display="block",setTimeout(m,1500)}catch(l){e.errorMsg.textContent=l.message,e.errorMsg.style.display="block"}finally{e.submitBtn.disabled=!1}}),e.textarea.addEventListener("input",()=>{e.errorMsg.style.display="none",e.textarea.classList.remove("error")}),console.log(`[feedtack] Injectable snippet loaded (v${t.version}, ${t.webhookUrl?"webhook":"clipboard"} mode)`)})();})();
|