@feelback/js
Version:
Client side js integration for Feelback service
2 lines (1 loc) • 12 kB
JavaScript
async function K(t,...e){return e.length>0&&(t=`${t}?$p=${JSON.stringify(e)}`),await q(fetch(t,{method:"GET"}))}async function _(t,...e){return await q(fetch(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(e)}))}async function q(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:K,post:_};function y(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 F="fbs-store";function S(t){return"contentId"in t?t.contentId:`${t.contentSetId}/${y(t.key)}`}var M=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=S(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(F)}remove(e){let n=typeof e=="string"?this.feelbacks?.findIndex(r=>r.feelbackId===e):(e=S(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(F))||[]}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(F,JSON.stringify(this.feelbacks))}catch{}}getFeelback(e){let n=typeof e=="string"?this.feelbacks?.find(r=>r.feelbackId===e):(e=S(e),this.feelbacks?.find(r=>r.key===e));if(n&&n.expire&&n.expire<Date.now()/1e3){this.remove(n.feelbackId);return}return n}},x,H;function v(t){return t??=H||"local",x&&H===t?x:(H=t,x=new M(t))}function ne(t,e){return v(e).getValue(t)}var R="https://api.feelback.dev/v0";async function N(t){let{endpoint:e=R,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:y(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 V(t){let{endpoint:e=R,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 j(t){let{endpoint:e=R}=t,n="contentId"in t?t.contentId:{contentSetId:t.contentSetId,key:y(t.key)};return await T.get(`${e}/feelbacks/getAggregates`,n)}function O(t,...e){console.warn(`[Feelback] ${t}`,...e)}function $(t,e){t instanceof Error&&(e=t,t=e?.message),console.error(`[Feelback] ${t}`,e)}function me(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=G(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 O("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(B(d,f)&&L.activate(k,f),d!==void 0&&!g){L.disableItem(w);return}w.addEventListener("click",z=>{let D=n.getRevocable(c),P=n.getValue(c);B(P,f)?D&&(s(o,1e3),V({endpoint:e,feelbackId:D.feelbackId}).then(()=>{L.deactivate(k,f),h.delta(m,f,-1),o.dispatchEvent(new Event("feedback-removed",{bubbles:!0}))},A=>{$("Cannot remove feelback",A)})):(s(o,1e3),N({endpoint:e,...c,value:f}).then(()=>{L.activate(k,f),b.behavior==="switch"&&u.switch.run({container:o,autoCancel:n.isRevokable(c)}),h.delta(m,String(P??"0"),-1),h.delta(m,f,1),o.dispatchEvent(new CustomEvent("feedback-sent",{detail:{...c,value:f},bubbles:!0}))},A=>{$("Cannot send feelback",A)}))})})}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}=X(g)||{};k&&(s(o,1e3),N({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=>{$("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?[]:(j({...c,endpoint:e}).then(g=>{d.forEach(([,m,k])=>{h.setItem(k,g?.[Number(m)])})},()=>{}),d)}}function G(t){let e=t.getAttribute("data-feelback");if(typeof e=="string")try{return JSON.parse(e)}catch{O("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{O("Invalid attributes for element",t);return}}function B(t,e){return t==null?!1:t===e||typeof t=="object"&&t?.value===e}var h={delta(t,e,n){let[,,r]=t.find(([a])=>a===e)||[];r&&h.set(t,e,Number(r.textContent)+n)},set(t,e,n){t.forEach(([r,,a])=>{r===e&&h.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}),E(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=W(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}),E(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=U(e.getAttribute("data-behavior-target")||".popup",e?.parentElement,t);n&&e.addEventListener("click",r=>{u.popup.run({source:e,target:n}),E(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=U(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}),E(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=C(e,".content"),a=l=>{r?.contains(l.target)||i(l)};document.addEventListener("click",a,{capture:!0}),u.dialog.closeActive=s;function i(l){E(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=C(n,"[data-feelback-field]"),a=[...p(n,":scope>button")].map(l=>[l.getAttribute("data-feelback-value"),l]),i=J(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")),E(l)})}}};function C(t,e){return t.querySelector(e)||void 0}function p(t,e){return t.querySelectorAll(e)}function E(t){t.preventDefault(),t.stopPropagation()}function U(t,...e){return t===":container"?e[e.length-1]||void 0:I(t,...e)}function W(t,...e){return t===":container"?[e[e.length-1]||void 0].filter(n=>!!n):J(t,...e)}function I(t,...e){if(typeof t!="string")return t||void 0;for(let n of e)if(n){let r=C(n,t);if(r)return r}}function J(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 X(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=Y(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 Y(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]:C(t,"button.active")?.getAttribute("data-feelback-value")};let r=t.getAttribute("data-feelback-value");if(r)return{[e]:r}}export{y as calculateContentKey,G as getConfigFromElement,j as getFeelbackAggregates,v as getFeelbackStore,ne as getLocalFeelbackValue,V as removeFeelback,N as sendFeelback,me as setupFeelback};