@feelback/js
Version:
Client side js integration for Feelback service
2 lines (1 loc) • 12.3 kB
JavaScript
;(()=>{var W=Object.defineProperty;var X=(t,e)=>{for(var n in e)W(t,n,{get:e[n],enumerable:!0})};var V={};X(V,{calculateContentKey:()=>E,getConfigFromElement:()=>_,getFeelbackAggregates:()=>O,getFeelbackStore:()=>v,getLocalFeelbackValue:()=>Q,removeFeelback:()=>$,sendFeelback:()=>C,setupFeelback:()=>q});async function Y(t,...e){return e.length>0&&(t=`${t}?$p=${JSON.stringify(e)}`),await U(fetch(t,{method:"GET"}))}async function z(t,...e){return await U(fetch(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(e)}))}async function U(t){if(t=await t,t.status>=400)throw new Error("[feelback] API error");if(t.status!==204)return await t.json()}var T={get:Y,post:z};function E(t,e){if(!t||t==="$auto")return e?.toString()||(typeof window<"u"?window.location.href:"/");if(t==="$path"){typeof e=="string"&&(e=new URL(e));let n=e||(typeof window<"u"?window.location:void 0);return n?`${n.origin}${n.pathname}`:"/"}return t}var S="fbs-store";function x(t){return"contentId"in t?t.contentId:`${t.contentSetId}/${E(t.key)}`}var R=class{constructor(e){this.feelbacks=void 0;if(e??="local",typeof window>"u"&&(e="memory"),e==="local")this.storage=window.localStorage;else if(e==="session")this.storage=window.sessionStorage;else{let n=()=>{};this.storage={getItem:n,setItem:n,removeItem:n,clear:n,key:n,length:0}}this.load()}add(e){let n=x(e.target),r=(this.feelbacks??=[]).findIndex(a=>a.key===n);r>=0&&this.feelbacks.splice(r,1),this.feelbacks.push({key:n,value:e.value,expire:e.expireIn&&e.expireIn>0?Math.floor(Date.now()/1e3)+e.expireIn:void 0,feelbackId:e.feelbackId,revokeToken:e.revokable?.token,revokeExpire:e.revokable?.expireAt&&Math.floor(new Date(e.revokable.expireAt).getTime()/1e3)||void 0}),this.save()}clear(){this.feelbacks?.splice(0,this.feelbacks.length),this.storage.removeItem(S)}remove(e){let n=typeof e=="string"?this.feelbacks?.findIndex(r=>r.feelbackId===e):(e=x(e),this.feelbacks?.findIndex(r=>r.key===e));n!==void 0&&n>=0&&(this.feelbacks.splice(n,1),this.save())}getValue(e){return this.getFeelback(e)?.value}isRevokable(e){return!!this.getRevocable(e)}getRevocable(e){let n=this.getFeelback(e);if(n&&n.revokeToken&&!(n.revokeExpire&&n.revokeExpire<Date.now()/1e3))return{feelbackId:n.feelbackId,revokeToken:n.revokeToken}}load(e){if(this.feelbacks&&!e)return;let n;try{n=JSON.parse(this.storage.getItem(S))||[]}catch{n=[]}this.feelbacks=n.filter(r=>!r.expire||r.expire>Date.now()/1e3),n.length!==this.feelbacks.length&&this.save()}save(){try{this.storage.setItem(S,JSON.stringify(this.feelbacks))}catch{}}getFeelback(e){let n=typeof e=="string"?this.feelbacks?.find(r=>r.feelbackId===e):(e=x(e),this.feelbacks?.find(r=>r.key===e));if(n&&n.expire&&n.expire<Date.now()/1e3){this.remove(n.feelbackId);return}return n}},H,M;function v(t){return t??=M||"local",H&&M===t?H:(M=t,H=new R(t))}function Q(t,e){return v(e).getValue(t)}var N="https://api.feelback.dev/v0";async function C(t){let{endpoint:e=N,store:n="local",revokable:r=!0,value:a,metadata:i,expireIn:s=3600}=t,l="contentId"in t?{contentId:t.contentId}:{contentSetId:t.contentSetId,key:E(t.key)},o=n&&n!=="none"&&v(n)||void 0,c=r&&o?.getRevocable(l)||void 0,b=c?await T.post(`${e}/feelbacks/edit`,{...c,value:a}):await T.post(`${e}/feelbacks/create`,{...l,value:a,context:i,revokable:r});o?.add({...b,target:l,value:a,expireIn:s})}async function $(t){let{endpoint:e=N,feelbackId:n}=t,r=t.revokeToken,a=v();if(!r){let i=a.getRevocable(n);if(!i)throw new Error("Cannot revoke feelback");r=i.revokeToken}await T.post(`${e}/feelbacks/remove`,{feelbackId:n,revokeToken:r}),a.remove(n)}async function O(t){let{endpoint:e=N}=t,n="contentId"in t?t.contentId:{contentSetId:t.contentSetId,key:E(t.key)};return await T.get(`${e}/feelbacks/getAggregates`,n)}function P(t,...e){console.warn(`[Feelback] ${t}`,...e)}function D(t,e){t instanceof Error&&(e=t,t=e?.message),console.error(`[Feelback] ${t}`,e)}function q(t){if(typeof window===void 0||typeof window.document===void 0)return;let e=t?.endpoint,n=v(t?.store);new Set([...p(document,"[data-feelback]"),...p(document,"[data-feelback-set]"),...p(document,"[data-feelback-content]")]).forEach(o=>{let c=_(o);if(!c)return;let b=c.contentId?{contentId:c.contentId}:{contentSetId:c.contentSetId,key:c.key};switch(c.component||"buttons"){case"buttons":a(o,b,c);break;case"form":i(o,b,c);break;default:return P("Unknown component: %s",c.component)}p(o,"[data-behavior-action],[data-feelback-action]").forEach(d=>{switch(d.getAttribute("data-behavior-action")||d.getAttribute("data-feelback-action")){case"switch":return u.switch.setup(o,d);case"popup":return u.popup.setup(o,d);case"dialog":return u.dialog.setup(o,d);case"toggle-class":return u.toggleClass.setup(o,d);case"set-field":return u.setField.setup(o,d)}})});function a(o,c,b){let d=n.getValue(c),g=n.isRevokable(c),m=l(o,c,b),k=[...p(o,"[data-feelback-value]").values()].map(f=>[f.getAttribute("data-feelback-value"),f]);k.forEach(([f,w])=>{if(J(d,f)&&L.activate(k,f),d!==void 0&&!g){L.disableItem(w);return}w.addEventListener("click",ne=>{let j=n.getRevocable(c),B=n.getValue(c);J(B,f)?j&&(s(o,1e3),$({endpoint:e,feelbackId:j.feelbackId}).then(()=>{L.deactivate(k,f),y.delta(m,f,-1),o.dispatchEvent(new Event("feedback-removed",{bubbles:!0}))},F=>{D("Cannot remove feelback",F)})):(s(o,1e3),C({endpoint:e,...c,value:f}).then(()=>{L.activate(k,f),b.behavior==="switch"&&u.switch.run({container:o,autoCancel:n.isRevokable(c)}),y.delta(m,String(B??"0"),-1),y.delta(m,f,1),o.dispatchEvent(new CustomEvent("feedback-sent",{detail:{...c,value:f},bubbles:!0}))},F=>{D("Cannot send feelback",F)}))})})}function i(o,c,b){let d=p(o,".feelback-form");d&&d.forEach(g=>{g.addEventListener("submit",m=>{m.preventDefault(),m.stopPropagation();let{value:k,metadata:f}=ee(g)||{};k&&(s(o,1e3),C({endpoint:e,...c,value:k,metadata:f}).then(()=>{u.switch.run({container:o}),b.behavior==="dialog"&&u.dialog.closeActive?.(),o.dispatchEvent(new CustomEvent("feelback-sent",{detail:{...c,value:k},bubbles:!0}))},w=>{D("Cannot send feelback",w)}))})})}function s(o,c){o.style.pointerEvents="none",setTimeout(()=>{o.style.pointerEvents=""},c)}function l(o,c,b){if(!b.showCount)return[];let d=[...p(o,"[data-feelback-count]").values()].map(g=>[g.getAttribute("data-feelback-count"),g.getAttribute("data-feelback-count-index"),g]);return d.length<1?[]:(O({...c,endpoint:e}).then(g=>{d.forEach(([,m,k])=>{y.setItem(k,g?.[Number(m)])})},()=>{}),d)}}function _(t){let e=t.getAttribute("data-feelback");if(typeof e=="string")try{return JSON.parse(e)}catch{P("Invalid config for element",t);return}let n,r=t.getAttribute("data-feelback-set");if(r)n={contentSetId:r,key:t.getAttribute("data-feelback-key")||void 0};else{let a=t.getAttribute("data-feelback-content");a&&(n={contentId:a})}if(n)try{return{...n,component:t.getAttribute("data-feelback-component"),revokable:t.getAttribute("data-feelback-revokable")!=="false",behavior:t.getAttribute("data-feelback-behavior")||void 0}}catch{P("Invalid attributes for element",t);return}}function J(t,e){return t==null?!1:t===e||typeof t=="object"&&t?.value===e}var y={delta(t,e,n){let[,,r]=t.find(([a])=>a===e)||[];r&&y.set(t,e,Number(r.textContent)+n)},set(t,e,n){t.forEach(([r,,a])=>{r===e&&y.setItem(a,n)})},setItem(t,e){t.textContent=(e||0).toFixed().toString(),t.setAttribute("data-feelback-count-value",(e||0).toString())}},L={activate(t,e){t.forEach(([n,r])=>{n===e?r.classList.add("active"):r.classList.remove("active","disabled")})},deactivate(t,e){t.forEach(([n,r])=>{n===e&&r.classList.remove("active","disabled")})},disableItem(t){t.classList.add("disabled")}},u={switch:{setup(t,e){let n=e.hasAttribute("data-behavior-source")?I(e.getAttribute("data-behavior-source"),e?.parentElement,t):e,r=I(e.getAttribute("data-behavior-target"),e?.parentElement,t);r&&e.addEventListener("click",a=>{u.switch.run({container:t,sideA:n,sideB:r}),h(a)})},run({container:t,sideA:e,sideB:n,autoCancel:r}){let a=I(e||"[data-behavior-switch-side='a']",t),i=I(n||"[data-behavior-switch-side='b']",t);if(!a||!i)return;let s=a.style.display;a.style.display="none",i.style.display="block",r&&setTimeout(()=>{i.style.display="none",a.style.display=s},5e3)}},toggleClass:{setup(t,e){let n=Z(e.getAttribute("data-behavior-target"),e?.parentElement,t);if(!n||n.length===0)return;let r=e.getAttribute("data-behavior-value")?.trim();if(!r)return;let a=["on","off"],i=r.split(/,|;/).map(s=>s.split(":")).filter(([s])=>a.includes(s));e.addEventListener("click",s=>{u.toggleClass.run({container:t,target:n,directives:i}),h(s)})},run({container:t,target:e,directives:n}){Array.isArray(e)||(e=[e]),e.forEach(r=>{n.forEach(([a,i])=>{a==="on"?r.classList.contains(i)||r.classList.add(i):a==="off"&&r.classList.contains(i)&&r.classList.remove(i)})})}},popup:{setup(t,e){let n=K(e.getAttribute("data-behavior-target")||".popup",e?.parentElement,t);n&&e.addEventListener("click",r=>{u.popup.run({source:e,target:n}),h(r)})},run({source:t,target:e}){e.style.display="block",e.style.top=t.offsetTop-(e.getBoundingClientRect().height-t.getBoundingClientRect().height)/2+"px",e.style.left=t.offsetLeft+"px",document.addEventListener("click",()=>{e.style.display="none"},{once:!0,capture:!1})}},dialog:{closeActive:void 0,setup(t,e){let n=K(e.getAttribute("data-behavior-target")||".dialog",e?.parentElement,t);if(!n)return;n.remove();let r=document.createElement("div");r.classList.add("feelback-style"),r.append(n),document.body.append(r),e.addEventListener("click",a=>{u.dialog.run({source:e,target:n}),h(a)})},run({source:t,target:e}){e.style.display="block";let n=[...p(e,"[data-behavior-action='cancel']")];n.forEach(l=>{l.addEventListener("click",i)});let r=A(e,".content"),a=l=>{r?.contains(l.target)||i(l)};document.addEventListener("click",a,{capture:!0}),u.dialog.closeActive=s;function i(l){h(l),s()}function s(){e.style.display="none",document.removeEventListener("click",a,{capture:!0}),n.forEach(l=>l.removeEventListener("click",i))}}},setField:{setup(t,e){let n=e.closest("[data-feelback-type='button-group']");if(!n)return;let r=A(n,"[data-feelback-field]"),a=[...p(n,":scope>button")].map(l=>[l.getAttribute("data-feelback-value"),l]),i=G(n.getAttribute("data-reveal"),t),s=e.getAttribute("data-feelback-value");e.addEventListener("click",l=>{L.activate(a,s),r&&(r.value=s),i.forEach(o=>o.classList.remove("hidden")),h(l)})}}};function A(t,e){return t.querySelector(e)||void 0}function p(t,e){return t.querySelectorAll(e)}function h(t){t.preventDefault(),t.stopPropagation()}function K(t,...e){return t===":container"?e[e.length-1]||void 0:I(t,...e)}function Z(t,...e){return t===":container"?[e[e.length-1]||void 0].filter(n=>!!n):G(t,...e)}function I(t,...e){if(typeof t!="string")return t||void 0;for(let n of e)if(n){let r=A(n,t);if(r)return r}}function G(t,...e){if(!t)return[];if(typeof t!="string")return Array.isArray(t)?t:[t];for(let n of e)if(n){let r=p(n,t);if(r.length)return[...r.values()]}return[]}function ee(t){let e=t.getAttribute("data-feelback-type")==="form-single",n=[...p(t,"[data-feelback-field]")],r,a;for(let i of n){let s=te(i);if(s){if(s?.$error)return;Object.entries(s).forEach(([l,o])=>{l.startsWith("#")?a={...a,[l.substring(1)]:o}:r={...r,[l]:o}})}}if(!(!r||Object.keys(r).length===0))return e&&(r=Object.values(r).pop()),{value:r,metadata:a}}function te(t){let e=t.getAttribute("data-feelback-field")||t.name;if(t.hasAttribute("data-feelback-metadata")&&(e="#"+t.getAttribute("data-feelback-metadata")),!e)return;if(t.tagName==="INPUT"){let a=t,i=a.value.trim()||void 0,s=a.required;return i?!i&&s?{$error:"required"}:{[e]:i}:void 0}if(t.tagName==="FIELDSET"&&(t.classList.contains("feelback-radio-group")||t.getAttribute("data-feelback-type")==="radio-group")){let a=t.hasAttribute("data-required"),i=t.querySelector("input[type='radio']:checked"),s=i?.value;return e=e||i?.name||"",!s&&a?{$error:"required"}:!s||!e?void 0:{[e]:s}}if(t.tagName==="TEXTAREA"){let a=t.value.trim()||void 0;return a?{[e]:a}:void 0}if(t.getAttribute("data-feelback-type")==="button-group")return{[e]:A(t,"button.active")?.getAttribute("data-feelback-value")};let r=t.getAttribute("data-feelback-value");if(r)return{[e]:r}}typeof window<"u"&&(window.feelback=V);typeof window<"u"&&window.addEventListener("DOMContentLoaded",()=>{q()});})();