nostr-components 0.2.6 → 0.2.7

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.
Files changed (33) hide show
  1. package/dist/assets/{base-styles-CBypR3FR.js → base-styles-BSEzBDsk.js} +3 -3
  2. package/dist/assets/{base-styles-CBypR3FR.js.map → base-styles-BSEzBDsk.js.map} +1 -1
  3. package/dist/assets/{copy-delegation-C4uvRTVM.js → copy-delegation-B7y2q5Kn.js} +5 -5
  4. package/dist/assets/{copy-delegation-C4uvRTVM.js.map → copy-delegation-B7y2q5Kn.js.map} +1 -1
  5. package/dist/assets/{dialog-likers-BjiCHFan.js → dialog-likers-BqDn2P_3.js} +4 -4
  6. package/dist/assets/{dialog-likers-BjiCHFan.js.map → dialog-likers-BqDn2P_3.js.map} +1 -1
  7. package/dist/assets/{nostr-service-pr_crY62.js → nostr-service-CnaPxjc6.js} +2 -2
  8. package/dist/assets/{nostr-service-pr_crY62.js.map → nostr-service-CnaPxjc6.js.map} +1 -1
  9. package/dist/assets/{nostr-user-component-BOdux8_6.js → nostr-user-component-Cbs97dlK.js} +2 -2
  10. package/dist/assets/{nostr-user-component-BOdux8_6.js.map → nostr-user-component-Cbs97dlK.js.map} +1 -1
  11. package/dist/assets/{pure-jrVhRVpB.js → pure-DPo-pzxM.js} +2 -2
  12. package/dist/assets/{pure-jrVhRVpB.js.map → pure-DPo-pzxM.js.map} +1 -1
  13. package/dist/assets/{theme-C1r1Zw8r.js → theme-BN1Bvweb.js} +2 -2
  14. package/dist/assets/{theme-C1r1Zw8r.js.map → theme-BN1Bvweb.js.map} +1 -1
  15. package/dist/assets/{user-resolver-C-E6KdwY.js → user-resolver-CMmbtY9Y.js} +2 -2
  16. package/dist/assets/{user-resolver-C-E6KdwY.js.map → user-resolver-CMmbtY9Y.js.map} +1 -1
  17. package/dist/assets/{zap-utils-B1sz0Abx.js → zap-utils-KFUD_vTU.js} +2 -2
  18. package/dist/assets/{zap-utils-B1sz0Abx.js.map → zap-utils-KFUD_vTU.js.map} +1 -1
  19. package/dist/components/nostr-comment.es.js +5 -5
  20. package/dist/components/nostr-dm.es.js +2 -2
  21. package/dist/components/nostr-follow-button.es.js +2 -2
  22. package/dist/components/nostr-follow-button.es.js.map +1 -1
  23. package/dist/components/nostr-like.es.js +4 -4
  24. package/dist/components/nostr-live-chat.es.js +3 -3
  25. package/dist/components/nostr-live-chat.es.js.map +1 -1
  26. package/dist/components/nostr-post.es.js +2 -2
  27. package/dist/components/nostr-profile-badge.es.js +3 -3
  28. package/dist/components/nostr-profile.es.js +2 -2
  29. package/dist/components/nostr-zap.es.js +4 -4
  30. package/dist/components/nostr-zap.es.js.map +1 -1
  31. package/dist/nostr-components.es.js +1 -1
  32. package/dist/nostr-components.es.js.map +1 -1
  33. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- var w=Object.defineProperty;var k=(t,r,e)=>r in t?w(t,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[r]=e;var c=(t,r,e)=>k(t,typeof r!="symbol"?r+"":r,e);import{D as v,c as p,g as m,M as E,N as T}from"./nostr-service-pr_crY62.js";const I=t=>{if(typeof t!="string"||!t.startsWith("npub1"))return"";try{const r=p.decode(t);if(r&&typeof r.data=="string")return r.data}catch(r){console.error("Failed to decode npub:",r)}return""};function M(t){if(!t||!N(t))return"";try{return p.npubEncode(t.toLowerCase())}catch(r){return console.error("Failed to encode hex to npub:",r),""}}function O(t="",r=3){const e=t.length;if(!t.startsWith("npub1"))return"Invalid nPub: expected npub1...";if(!S(t))return"Invalid nPub";let s="npub1";for(let n=5;n<r+5;n++)s+=t[n];s+="...";let o="";for(let n=e-1;n>=e-r;n--)o=t[n]+o;return s+=o,s}async function z(t,r){const e=await t.fetchEvents({kinds:[m.Repost],"#e":[r||""]}),s=d=>d.tags.filter(g=>g[0]==="p").length===1,o=d=>d.tags.filter(g=>g[0]==="e").length===1,n=Array.from(e).filter(s).length,a=await t.fetchEvents({kinds:[m.Reaction],"#e":[r||""]}),i=0,u=await t.fetchEvents({kinds:[m.Text],"#e":[r||""]}),l=Array.from(u).filter(o).length;return{likes:a.size,reposts:n,zaps:i/E,replies:l}}function C(t){if(t){const r=t.split(",").map(e=>e.trim()).filter(Boolean).filter(y);return r.length?Array.from(new Set(r)):[...v]}return[...v]}function L(t){const r=t==null?void 0:t.trim().toLowerCase();return r==="light"||r==="dark"?r:"light"}function P(t){return t===null?!1:t===""||t.toLowerCase()==="true"}function B(t){const r=document.createElement("div");return r.textContent=t,r.innerHTML}function F(t){try{const r=new URL(t);return["http:","https:"].includes(r.protocol)}catch{return!1}}function y(t){try{const r=new URL(t);return r.protocol==="wss:"||r.protocol==="ws:"}catch{return!1}}function N(t){return/^[0-9a-fA-F]+$/.test(t)&&t.length===64}function S(t){try{const{type:r}=p.decode(t);return r==="npub"}catch{return!1}}function j(t){return/^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+\.[a-zA-Z]{2,}$/.test(t)}function b(t,r){try{const{type:e}=p.decode(t);return e===r}catch{return!1}}function U(t){return b(t,"note")}function D(t){return b(t,"nevent")}function H(t){return navigator.clipboard.writeText(t)}function q(t){try{const r=Date.now(),e=t*1e3,s=r-e,o=Math.floor(s/1e3);if(o<60)return"just now";if(o<3600){const a=Math.floor(o/60);return`${a} ${a===1?"min":"mins"} ago`}if(o<86400){const a=Math.floor(o/3600);return`${a} ${a===1?"hour":"hours"} ago`}if(o<2592e3){const a=Math.floor(o/86400);return`${a} ${a===1?"day":"days"} ago`}if(o<31536e3){const a=Math.floor(o/2592e3);return`${a} ${a===1?"month":"months"} ago`}const n=Math.floor(o/31536e3);return`${n} ${n===1?"year":"years"} ago`}catch(r){return console.error("Error formatting relative time:",r),"unknown"}}var f=(t=>(t[t.Idle=0]="Idle",t[t.Loading=1]="Loading",t[t.Ready=2]="Ready",t[t.Error=3]="Error",t))(f||{});const $="nc:status";class K extends HTMLElement{constructor(e=!0){super();c(this,"nostrService",T.getInstance());c(this,"theme","light");c(this,"errorMessage","");c(this,"nostrReady");c(this,"nostrReadyResolve");c(this,"nostrReadyReject");c(this,"conn",this.channel("connection"));c(this,"_statuses",new Map);c(this,"_overall",0);c(this,"connectSeq",0);e&&this.attachShadow({mode:"open"}),this.resetNostrReadyBarrier()}static get observedAttributes(){return["data-theme","relays"]}connectedCallback(){this.validateInputs()&&(this.getTheme(),this.conn.get()===0&&this.connectToNostr())}disconnectedCallback(){this.shadowRoot&&this._delegated&&this._delegated.clear()}attributeChangedCallback(e,s,o){s!==o&&(e==="data-theme"||e==="relays")&&this.validateInputs()&&(e==="relays"&&(this.resetNostrReadyBarrier(),this.connectToNostr()),e==="data-theme"&&(this.getTheme(),this.render()))}setStatusFor(e,s,o){const n=this._statuses.get(e);if(!(n!==s||s===3&&!!o))return;this._statuses.set(e,s),s===3&&o?this.errorMessage=o:n===3&&s!==3&&(this.errorMessage="");const i=`${e}-status`,u=f[s].toLowerCase();this.getAttribute(i)!==u&&this.setAttribute(i,u);const l=this.computeOverall(),d=f[l].toLowerCase();this._overall!==l?(this._overall=l,this.setAttribute("status",d),this.onStatusChange(l)):l===3&&o&&this.onStatusChange(l),this.dispatchEvent(new CustomEvent($,{detail:{key:e,status:s,all:this.snapshotStatuses(),overall:this._overall,errorMessage:this.errorMessage||void 0},bubbles:!0,composed:!0}))}getStatusFor(e){return this._statuses.get(e)??0}snapshotStatuses(){return Object.fromEntries(this._statuses.entries())}onStatusChange(e){}onNostrRelaysConnected(){}computeOverall(){const e=[...this._statuses.values()];return e.includes(3)?3:e.includes(1)?1:e.includes(2)?2:0}initChannelStatus(e,s,o={reflectOverall:!1}){if(this._statuses.set(e,s),this.setAttribute(`${e}-status`,f[s].toLowerCase()),o.reflectOverall){const n=this.computeOverall();this._overall=n,this.setAttribute("status",f[n].toLowerCase())}}channel(e){return{set:(s,o)=>this.setStatusFor(e,s,o),get:()=>this.getStatusFor(e)}}validateInputs(){const e=this.getAttribute("data-theme")||"light",s=this.getAttribute("relays"),o=this.tagName.toLowerCase();if(e==="light"||e==="dark"){if(s&&typeof s!="string")return this.conn.set(3,"Invalid relays list"),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1;if(s){const a=s.split(",").map(i=>i.trim()).filter(Boolean).filter(i=>!y(i));if(a.length>0){const i=a.join(", ");return this.conn.set(3,`Invalid relay URLs: ${i}. Relay URLs must start with 'wss://' or 'ws://'`),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1}}}else return this.conn.set(3,`Invalid theme '${e}'. Accepted values are 'light', 'dark'`),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1;return this.errorMessage="",!0}async connectToNostr(){var s,o;const e=++this.connectSeq;this.conn.set(1);try{if(await this.nostrService.connectToNostr(this.getRelays()),e!==this.connectSeq)return;this.conn.set(2),(s=this.nostrReadyResolve)==null||s.call(this);try{this.onNostrRelaysConnected()}catch(n){console.error("Error in onNostrRelaysConnected hook:",n)}}catch(n){if(e!==this.connectSeq)return;console.error("Failed to connect to Nostr relays:",n),this.conn.set(3,"Failed to connect to relays"),(o=this.nostrReadyReject)==null||o.call(this,n)}}ensureNostrConnected(){return this.nostrReady}getRelays(){return C(this.getAttribute("relays"))}getTheme(){this.theme=L(this.getAttribute("data-theme"))}delegateEvent(e,s,o){var i;const n=this.shadowRoot;if(!n)return;const a=`${e}:${s}`;(i=this._delegated)!=null&&i.has(a)||(this._delegated||(this._delegated=new Set),this._delegated.add(a),n.addEventListener(e,u=>{u.target.closest(s)&&o(u)}))}addDelegatedListener(e,s,o){this.delegateEvent(e,s,o)}renderError(e){return`Error: ${e}`}updateHostClasses(){const e=this.computeOverall()===1,s=this.computeOverall()===3,o=this.computeOverall()===2;this.classList.remove("is-clickable","is-disabled","is-error"),e?this.classList.add("is-disabled"):s?this.classList.add("is-error"):o&&this.classList.add("is-clickable")}render(){this.updateHostClasses(),this.renderContent()}handleNjumpClick(e,s,o){if(this.computeOverall()!==2)return;const n=new CustomEvent(e,{detail:s,bubbles:!0,composed:!0,cancelable:!0});this.dispatchEvent(n)&&window.open(`https://njump.me/${o}`,"_blank","noopener,noreferrer")}resetNostrReadyBarrier(){this.connectSeq++,this.nostrReady=new Promise((e,s)=>{this.nostrReadyResolve=e,this.nostrReadyReject=s})}}function x(){return`
1
+ var w=Object.defineProperty;var k=(t,r,e)=>r in t?w(t,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[r]=e;var c=(t,r,e)=>k(t,typeof r!="symbol"?r+"":r,e);import{c as v,a as p,e as m,M as E,b as T}from"./nostr-service-CnaPxjc6.js";const I=t=>{if(typeof t!="string"||!t.startsWith("npub1"))return"";try{const r=p.decode(t);if(r&&typeof r.data=="string")return r.data}catch(r){console.error("Failed to decode npub:",r)}return""};function M(t){if(!t||!S(t))return"";try{return p.npubEncode(t.toLowerCase())}catch(r){return console.error("Failed to encode hex to npub:",r),""}}function O(t="",r=3){const e=t.length;if(!t.startsWith("npub1"))return"Invalid nPub: expected npub1...";if(!N(t))return"Invalid nPub";let s="npub1";for(let n=5;n<r+5;n++)s+=t[n];s+="...";let o="";for(let n=e-1;n>=e-r;n--)o=t[n]+o;return s+=o,s}async function z(t,r){const e=await t.fetchEvents({kinds:[m.Repost],"#e":[r||""]}),s=d=>d.tags.filter(g=>g[0]==="p").length===1,o=d=>d.tags.filter(g=>g[0]==="e").length===1,n=Array.from(e).filter(s).length,a=await t.fetchEvents({kinds:[m.Reaction],"#e":[r||""]}),i=0,u=await t.fetchEvents({kinds:[m.Text],"#e":[r||""]}),l=Array.from(u).filter(o).length;return{likes:a.size,reposts:n,zaps:i/E,replies:l}}function C(t){if(t){const r=t.split(",").map(e=>e.trim()).filter(Boolean).filter(y);return r.length?Array.from(new Set(r)):[...v]}return[...v]}function L(t){const r=t==null?void 0:t.trim().toLowerCase();return r==="light"||r==="dark"?r:"light"}function P(t){return t===null?!1:t===""||t.toLowerCase()==="true"}function B(t){const r=document.createElement("div");return r.textContent=t,r.innerHTML}function F(t){try{const r=new URL(t);return["http:","https:"].includes(r.protocol)}catch{return!1}}function y(t){try{const r=new URL(t);return r.protocol==="wss:"||r.protocol==="ws:"}catch{return!1}}function S(t){return/^[0-9a-fA-F]+$/.test(t)&&t.length===64}function N(t){try{const{type:r}=p.decode(t);return r==="npub"}catch{return!1}}function j(t){return/^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+\.[a-zA-Z]{2,}$/.test(t)}function b(t,r){try{const{type:e}=p.decode(t);return e===r}catch{return!1}}function U(t){return b(t,"note")}function D(t){return b(t,"nevent")}function H(t){return navigator.clipboard.writeText(t)}function q(t){try{const r=Date.now(),e=t*1e3,s=r-e,o=Math.floor(s/1e3);if(o<60)return"just now";if(o<3600){const a=Math.floor(o/60);return`${a} ${a===1?"min":"mins"} ago`}if(o<86400){const a=Math.floor(o/3600);return`${a} ${a===1?"hour":"hours"} ago`}if(o<2592e3){const a=Math.floor(o/86400);return`${a} ${a===1?"day":"days"} ago`}if(o<31536e3){const a=Math.floor(o/2592e3);return`${a} ${a===1?"month":"months"} ago`}const n=Math.floor(o/31536e3);return`${n} ${n===1?"year":"years"} ago`}catch(r){return console.error("Error formatting relative time:",r),"unknown"}}var f=(t=>(t[t.Idle=0]="Idle",t[t.Loading=1]="Loading",t[t.Ready=2]="Ready",t[t.Error=3]="Error",t))(f||{});const $="nc:status";class K extends HTMLElement{constructor(e=!0){super();c(this,"nostrService",T.getInstance());c(this,"theme","light");c(this,"errorMessage","");c(this,"nostrReady");c(this,"nostrReadyResolve");c(this,"nostrReadyReject");c(this,"conn",this.channel("connection"));c(this,"_statuses",new Map);c(this,"_overall",0);c(this,"connectSeq",0);e&&this.attachShadow({mode:"open"}),this.resetNostrReadyBarrier()}static get observedAttributes(){return["data-theme","relays"]}connectedCallback(){this.validateInputs()&&(this.getTheme(),this.conn.get()===0&&this.connectToNostr())}disconnectedCallback(){this.shadowRoot&&this._delegated&&this._delegated.clear()}attributeChangedCallback(e,s,o){s!==o&&(e==="data-theme"||e==="relays")&&this.validateInputs()&&(e==="relays"&&(this.resetNostrReadyBarrier(),this.connectToNostr()),e==="data-theme"&&(this.getTheme(),this.render()))}setStatusFor(e,s,o){const n=this._statuses.get(e);if(!(n!==s||s===3&&!!o))return;this._statuses.set(e,s),s===3&&o?this.errorMessage=o:n===3&&s!==3&&(this.errorMessage="");const i=`${e}-status`,u=f[s].toLowerCase();this.getAttribute(i)!==u&&this.setAttribute(i,u);const l=this.computeOverall(),d=f[l].toLowerCase();this._overall!==l?(this._overall=l,this.setAttribute("status",d),this.onStatusChange(l)):l===3&&o&&this.onStatusChange(l),this.dispatchEvent(new CustomEvent($,{detail:{key:e,status:s,all:this.snapshotStatuses(),overall:this._overall,errorMessage:this.errorMessage||void 0},bubbles:!0,composed:!0}))}getStatusFor(e){return this._statuses.get(e)??0}snapshotStatuses(){return Object.fromEntries(this._statuses.entries())}onStatusChange(e){}onNostrRelaysConnected(){}computeOverall(){const e=[...this._statuses.values()];return e.includes(3)?3:e.includes(1)?1:e.includes(2)?2:0}initChannelStatus(e,s,o={reflectOverall:!1}){if(this._statuses.set(e,s),this.setAttribute(`${e}-status`,f[s].toLowerCase()),o.reflectOverall){const n=this.computeOverall();this._overall=n,this.setAttribute("status",f[n].toLowerCase())}}channel(e){return{set:(s,o)=>this.setStatusFor(e,s,o),get:()=>this.getStatusFor(e)}}validateInputs(){const e=this.getAttribute("data-theme")||"light",s=this.getAttribute("relays"),o=this.tagName.toLowerCase();if(e==="light"||e==="dark"){if(s&&typeof s!="string")return this.conn.set(3,"Invalid relays list"),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1;if(s){const a=s.split(",").map(i=>i.trim()).filter(Boolean).filter(i=>!y(i));if(a.length>0){const i=a.join(", ");return this.conn.set(3,`Invalid relay URLs: ${i}. Relay URLs must start with 'wss://' or 'ws://'`),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1}}}else return this.conn.set(3,`Invalid theme '${e}'. Accepted values are 'light', 'dark'`),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1;return this.errorMessage="",!0}async connectToNostr(){var s,o;const e=++this.connectSeq;this.conn.set(1);try{if(await this.nostrService.connectToNostr(this.getRelays()),e!==this.connectSeq)return;this.conn.set(2),(s=this.nostrReadyResolve)==null||s.call(this);try{this.onNostrRelaysConnected()}catch(n){console.error("Error in onNostrRelaysConnected hook:",n)}}catch(n){if(e!==this.connectSeq)return;console.error("Failed to connect to Nostr relays:",n),this.conn.set(3,"Failed to connect to relays"),(o=this.nostrReadyReject)==null||o.call(this,n)}}ensureNostrConnected(){return this.nostrReady}getRelays(){return C(this.getAttribute("relays"))}getTheme(){this.theme=L(this.getAttribute("data-theme"))}delegateEvent(e,s,o){var i;const n=this.shadowRoot;if(!n)return;const a=`${e}:${s}`;(i=this._delegated)!=null&&i.has(a)||(this._delegated||(this._delegated=new Set),this._delegated.add(a),n.addEventListener(e,u=>{u.target.closest(s)&&o(u)}))}addDelegatedListener(e,s,o){this.delegateEvent(e,s,o)}renderError(e){return`Error: ${e}`}updateHostClasses(){const e=this.computeOverall()===1,s=this.computeOverall()===3,o=this.computeOverall()===2;this.classList.remove("is-clickable","is-disabled","is-error"),e?this.classList.add("is-disabled"):s?this.classList.add("is-error"):o&&this.classList.add("is-clickable")}render(){this.updateHostClasses(),this.renderContent()}handleNjumpClick(e,s,o){if(this.computeOverall()!==2)return;const n=new CustomEvent(e,{detail:s,bubbles:!0,composed:!0,cancelable:!0});this.dispatchEvent(n)&&window.open(`https://njump.me/${o}`,"_blank","noopener,noreferrer")}resetNostrReadyBarrier(){this.connectSeq++,this.nostrReady=new Promise((e,s)=>{this.nostrReadyResolve=e,this.nostrReadyReject=s})}}function x(){return`
2
2
  :host {
3
3
  /* === GENERIC DESIGN TOKENS === */
4
4
  --nostrc-color-background: #ffffff;
@@ -141,5 +141,5 @@ var w=Object.defineProperty;var k=(t,r,e)=>r in t?w(t,r,{enumerable:!0,configura
141
141
  font-size: 2em;
142
142
  margin: auto;
143
143
  }
144
- `};export{K as N,f as a,S as b,F as c,H as d,B as e,q as f,V as g,M as h,N as i,U as j,D as k,z as l,O as m,I as n,P as p,j as v};
145
- //# sourceMappingURL=base-styles-CBypR3FR.js.map
144
+ `};export{f as N,S as a,D as b,K as c,z as d,B as e,H as f,V as g,I as h,F as i,M as j,q as k,j as l,O as m,N as n,P as p,U as v};
145
+ //# sourceMappingURL=base-styles-BSEzBDsk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-styles-CBypR3FR.js","sources":["../../src/common/utils.ts","../../src/base/base-component/nostr-base-component.ts","../../src/common/base-styles.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport NDK, { NDKKind, NDKEvent } from '@nostr-dev-kit/ndk';\nimport { nip19 } from \"nostr-tools\";\n\n\nimport { Theme } from './types';\nimport { DEFAULT_RELAYS, MILLISATS_PER_SAT } from './constants';\n\nexport const decodeNpub = (npub: string): string => {\n if (typeof npub !== 'string' || !npub.startsWith('npub1')) {\n return '';\n }\n\n try {\n const decoded = nip19.decode(npub);\n if (decoded && typeof decoded.data === 'string') {\n return decoded.data;\n }\n } catch (error) {\n console.error('Failed to decode npub:', error);\n }\n \n return '';\n};\n\n/**\n * Convert hex pubkey to npub\n */\nexport function hexToNpub(hex: string): string {\n if (!hex || !isValidHex(hex)) return '';\n try {\n return nip19.npubEncode(hex.toLowerCase());\n } catch (error) {\n console.error('Failed to encode hex to npub:', error);\n return '';\n }\n}\n\n// Could be npub, note1, naddr, nsec, etc.,\nexport const decodeNip19Entity = (entity: string): any => {\n if (typeof entity !== 'string' || !/^[a-z0-9]+1[ac-hj-np-z02-9]+/.test(entity)) {\n return null;\n }\n\n try {\n const decoded = nip19.decode(entity);\n return decoded?.data ?? null;\n } catch (error) {\n console.error('Failed to decode NIP-19 entity:', error);\n return null;\n }\n};\n\nexport function maskNPub(npubString: string = '', length = 3) {\n const npubLength = npubString.length;\n\n if (!npubString.startsWith('npub1')) {\n return 'Invalid nPub: expected npub1...';\n }\n\n if (!validateNpub(npubString)) {\n return 'Invalid nPub';\n }\n\n let result = 'npub1';\n\n for (let i = 5; i < length + 5; i++) {\n result += npubString[i];\n }\n\n result += '...';\n\n let suffix = '';\n for (let i = npubLength - 1; i >= npubLength - length; i--) {\n suffix = npubString[i] + suffix;\n }\n\n result += suffix;\n\n return result;\n}\n\nexport type Stats = {\n likes: number;\n reposts: number;\n zaps: number;\n replies: number;\n};\n\nexport async function getPostStats(ndk: NDK, postId: string): Promise<Stats> {\n const reposts = await ndk.fetchEvents({\n kinds: [NDKKind.Repost],\n '#e': [postId || ''],\n });\n\n const isDirectRepost = (repost: NDKEvent): boolean => {\n const pTagCounts = repost.tags.filter(tag => tag[0] === 'p').length;\n return pTagCounts === 1;\n };\n\n const isDirectReply = (reply: NDKEvent): boolean => {\n const eTagsCount = reply.tags.filter(tag => tag[0] === 'e').length;\n return eTagsCount === 1;\n };\n\n // Only take the count of direct reposts\n const repostsCount = Array.from(reposts).filter(isDirectRepost).length;\n\n const likes = await ndk.fetchEvents({\n kinds: [NDKKind.Reaction],\n '#e': [postId || ''],\n });\n\n // TODO: Add zap receipt validation - https://github.com/nostr-protocol/nips/blob/master/57.md#appendix-f-validating-zap-receipts\n // const zaps = await ndk.fetchEvents({\n // kinds: [NDKKind.Zap],\n // '#e': [postId || '']\n // });\n\n // const zapAmount = Array.from(zaps).reduce((prev, curr) => {\n // const bolt11Tag = curr.getMatchingTags('bolt11');\n\n // if(\n // !bolt11Tag ||\n // !Array.isArray(bolt11Tag) ||\n // bolt11Tag.length === 0 ||\n // !bolt11Tag[0] ||\n // !Array.isArray(bolt11Tag[0]) ||\n // (bolt11Tag[0] as string[]).length === 0\n // ) {\n // return prev;\n // }\n\n // const bolt11 = bolt11Tag[0][1];\n\n // const decodedbol11 = decode(bolt11);\n\n // const amountSection = decodedbol11.sections.find(section => section.name === 'amount');\n\n // if(amountSection) {\n // const millisats = Number(amountSection.value);\n\n // return prev + millisats;\n // }\n\n // return prev;\n // }, 0);\n\n const zapAmount = 0;\n\n const replies = await ndk.fetchEvents({\n kinds: [NDKKind.Text],\n '#e': [postId || ''],\n });\n\n // Only take the direct replies\n // https://github.com/nostr-protocol/nips/blob/master/10.md#positional-e-tags-deprecated\n const replyCount = Array.from(replies).filter(isDirectReply).length;\n\n return {\n likes: likes.size,\n reposts: repostsCount,\n zaps: zapAmount / MILLISATS_PER_SAT,\n replies: replyCount,\n };\n}\n\nexport function parseRelays(relaysAttr: string | null): string[] {\n if (relaysAttr) {\n const list = relaysAttr\n .split(',')\n .map(r => r.trim())\n .filter(Boolean)\n .filter(isValidRelayUrl);\n // fall back to defaults if user provided no valid entries\n return list.length ? Array.from(new Set(list)) : [...DEFAULT_RELAYS];\n }\n return [...DEFAULT_RELAYS];\n}\n\nexport function parseTheme(themeAttr: string | null): Theme {\n\n const theme = themeAttr?.trim().toLowerCase();\n\n if (theme === 'light' || theme === 'dark') {\n return theme;\n }\n\n return 'light';\n}\n\nexport function parseBooleanAttribute(attr: string | null): boolean {\n // Handles: \"true\", \"\", null, \"false\"\n if (attr === null) return false;\n if (attr === '' || attr.toLowerCase() === 'true') return true;\n return false;\n}\n\nexport function escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n\nexport function isValidUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return ['http:', 'https:'].includes(parsed.protocol);\n } catch {\n return false;\n }\n}\n\nexport function isValidRelayUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return u.protocol === 'wss:' || u.protocol === 'ws:';\n } catch {\n return false;\n }\n}\n\nexport function isValidHex(hex: string): boolean {\n return /^[0-9a-fA-F]+$/.test(hex) && hex.length === 64;\n}\n\nexport function validateNpub(npub: string): boolean {\n try {\n const { type } = nip19.decode(npub);\n return type === 'npub';\n } catch (e) {\n return false;\n }\n}\n\nexport function validateNip05(nip05: string): boolean {\n const nip05Regex = /^[a-zA-Z0-9_\\-\\.]+@[a-zA-Z0-9_\\-\\.]+\\.[a-zA-Z]{2,}$/;\n return nip05Regex.test(nip05);\n}\n\nfunction validateBech32OfType(input: string, expected: 'note' | 'nevent'): boolean {\n try {\n const { type } = nip19.decode(input);\n return type === expected;\n } catch {\n return false;\n }\n}\n\nexport function validateNoteId(noteId: string): boolean {\n return validateBech32OfType(noteId, 'note');\n}\n\nexport function validateEventId(eventId: string): boolean {\n return validateBech32OfType(eventId, 'nevent');\n}\n\nexport function copyToClipboard(text: string): Promise<void> {\n return navigator.clipboard.writeText(text)\n}\n\n/**\n * Format timestamp as relative time (e.g., \"2 mins ago\", \"1 month ago\")\n * @param ts Timestamp in seconds\n * @returns Formatted relative time string\n */\nexport function formatRelativeTime(ts: number): string {\n try {\n const now = Date.now();\n const messageTime = ts * 1000;\n const diffMs = now - messageTime;\n\n // Convert to seconds\n const diffSec = Math.floor(diffMs / 1000);\n\n if (diffSec < 60) {\n return 'just now';\n }\n\n // Minutes\n if (diffSec < 3600) {\n const mins = Math.floor(diffSec / 60);\n return `${mins} ${mins === 1 ? 'min' : 'mins'} ago`;\n }\n\n // Hours\n if (diffSec < 86400) {\n const hours = Math.floor(diffSec / 3600);\n return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;\n }\n\n // Days\n if (diffSec < 2592000) { // ~30 days\n const days = Math.floor(diffSec / 86400);\n return `${days} ${days === 1 ? 'day' : 'days'} ago`;\n }\n\n // Months\n if (diffSec < 31536000) { // ~365 days\n const months = Math.floor(diffSec / 2592000);\n return `${months} ${months === 1 ? 'month' : 'months'} ago`;\n }\n\n // Years\n const years = Math.floor(diffSec / 31536000);\n return `${years} ${years === 1 ? 'year' : 'years'} ago`;\n } catch (error) {\n console.error('Error formatting relative time:', error);\n return 'unknown';\n }\n}\n","// SPDX-License-Identifier: MIT\n\nimport { NostrService } from '../../common/nostr-service';\nimport { Theme } from '../../common/types';\nimport { parseRelays, parseTheme, isValidRelayUrl } from '../../common/utils';\n\nexport enum NCStatus {\n Idle, // 0\n Loading, // 1\n Ready, // 2\n Error, // 3\n}\n\nconst EVT_STATUS = 'nc:status';\n\n/**\n * NostrBaseComponent\n * ==================\n * Foundation for all Nostr web components in this library.\n *\n * Overview\n * - Manages relay connectivity via a shared `NostrService`.\n * - Parses common attributes (`theme`, `relays`) and applies theme.\n * - Provides a generic status management logic, that is extensible by derived classes.\n * - Offers small utilities useful to subclasses (event delegation, error snippet).\n *\n * Observed attributes\n * - `data-theme` — \"light\" | \"dark\"\n * - `relays` — CSV of relay URLs\n * \n * Events\n * - `nc:status` — from base, reflects connection and user/profile loading status\n * \n * TODO: Is this class doing too much work? Time to split into smaller components?\n */\nexport abstract class NostrBaseComponent extends HTMLElement {\n\n protected nostrService: NostrService = NostrService.getInstance();\n\n protected theme: Theme = 'light';\n protected errorMessage: string = '';\n\n protected nostrReady!: Promise<void>;\n protected nostrReadyResolve?: () => void;\n protected nostrReadyReject?: (e: unknown) => void;\n\n protected conn = this.channel('connection');\n\n private _statuses = new Map<string, NCStatus>();\n private _overall: NCStatus = NCStatus.Idle;\n\n // guard to ignore stale connects\n private connectSeq = 0;\n\n constructor(shadow: boolean = true) {\n super();\n if (shadow) this.attachShadow({ mode: 'open' });\n this.resetNostrReadyBarrier();\n }\n\n /** Lifecycle methods */\n static get observedAttributes() {\n return ['data-theme', 'relays'];\n }\n\n connectedCallback() {\n if (this.validateInputs()) {\n this.getTheme();\n // Avoid duplicate connects if a subclass handles it\n if (this.conn.get() === NCStatus.Idle) {\n void this.connectToNostr();\n }\n }\n }\n\n disconnectedCallback() {\n // Clean up delegated event listeners if shadow root exists\n if (this.shadowRoot && (this as any)._delegated) {\n (this as any)._delegated.clear();\n }\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null\n ) {\n if (oldValue === newValue) return;\n\n if (name === 'data-theme' || name === 'relays') {\n if (this.validateInputs()) {\n if (name === 'relays') {\n this.resetNostrReadyBarrier();\n void this.connectToNostr();\n }\n\n if (name === 'data-theme') {\n this.getTheme();\n this.render();\n }\n }\n }\n }\n\n /** Status map API */\n\n protected setStatusFor(key: string, next: NCStatus, error?: string) {\n const prev = this._statuses.get(key);\n const changed = prev !== next || (next === NCStatus.Error && !!error);\n\n if (!changed) return;\n\n this._statuses.set(key, next);\n\n if (next === NCStatus.Error && error) {\n this.errorMessage = error;\n } else if (prev === NCStatus.Error && next !== NCStatus.Error) {\n this.errorMessage = '';\n }\n\n // Reflect per-key attribute, e.g. user-status=\"loading\"\n const perKeyAttr = `${key}-status`;\n const perKeyVal = NCStatus[next].toLowerCase();\n if (this.getAttribute(perKeyAttr) !== perKeyVal) {\n this.setAttribute(perKeyAttr, perKeyVal);\n }\n\n // Compute & reflect overall for backward-compat CSS\n const overall = this.computeOverall();\n const overallVal = NCStatus[overall].toLowerCase();\n if (this._overall !== overall) {\n this._overall = overall;\n this.setAttribute('status', overallVal);\n this.onStatusChange(overall);\n } else if (overall === NCStatus.Error && error) {\n // propagate error updates even if overall state didn't flip\n this.onStatusChange(overall);\n }\n\n // Emit a single event with structured detail\n this.dispatchEvent(new CustomEvent(EVT_STATUS, {\n detail: {\n key,\n status: next,\n all: this.snapshotStatuses(),\n overall: this._overall,\n errorMessage: this.errorMessage || undefined,\n },\n bubbles: true,\n composed: true,\n }));\n }\n\n protected getStatusFor(key: string): NCStatus {\n return this._statuses.get(key) ?? NCStatus.Idle;\n }\n\n protected snapshotStatuses(): Record<string, NCStatus> {\n return Object.fromEntries(this._statuses.entries());\n }\n\n /** Overall status change hook */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected onStatusChange(_overall: NCStatus) { }\n\n /** Hook for subclasses to react when relays are connected */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected onNostrRelaysConnected() { }\n\n protected computeOverall(): NCStatus {\n const vals = [...this._statuses.values()];\n if (vals.includes(NCStatus.Error)) return NCStatus.Error;\n if (vals.includes(NCStatus.Loading)) return NCStatus.Loading;\n if (vals.includes(NCStatus.Ready)) return NCStatus.Ready;\n return NCStatus.Idle;\n }\n\n /**\n * Before first channel.set(), the map won't be set and hence get() will return `idle`.\n * In some cases (or maybe in all cases), this is not ideal.\n * In UserComponent, 'user' not set and hence returning `idle`, while\n * 'connect' switched between idle -> loading -> ready\n * and then\n * 'user' switches to loading -> ready,\n * results in loading -> ready -> loading -> ready in profile-badge onStatusChange.\n * \n * To avoid this, this function is called from UserComponent constructor,\n * to set default value as 'loading' without emitting onStatusChange event.\n * \n * Makes sense?\n * Try commenting this function call in UserComponent()\n * and add a log in ProfileBadge :: onStatusChange. You will get it.\n */\n protected initChannelStatus(key: string, status: NCStatus, opts = { reflectOverall: false }) {\n this._statuses.set(key, status);\n this.setAttribute(`${key}-status`, NCStatus[status].toLowerCase());\n if (opts.reflectOverall) {\n const overall = this.computeOverall();\n this._overall = overall;\n this.setAttribute('status', NCStatus[overall].toLowerCase());\n }\n }\n\n protected channel(key: string) {\n return {\n set: (s: NCStatus, e?: string) => this.setStatusFor(key, s, e),\n get: () => this.getStatusFor(key)\n };\n }\n\n /** Protected methods */\n protected validateInputs(): boolean {\n const theme = this.getAttribute('data-theme') || 'light';\n const relays = this.getAttribute('relays');\n const tagName = this.tagName.toLowerCase();\n\n if (!(theme === 'light' || theme === 'dark')) {\n this.conn.set(NCStatus.Error, `Invalid theme '${theme}'. Accepted values are 'light', 'dark'`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n } else if (relays && typeof relays != 'string') {\n this.conn.set(NCStatus.Error, `Invalid relays list`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n } else if (relays) {\n const relayList = relays.split(',').map(r => r.trim()).filter(Boolean);\n const invalidRelays = relayList.filter(relay => !isValidRelayUrl(relay));\n \n if (invalidRelays.length > 0) {\n const invalidRelaysList = invalidRelays.join(', ');\n this.conn.set(NCStatus.Error, `Invalid relay URLs: ${invalidRelaysList}. Relay URLs must start with 'wss://' or 'ws://'`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n }\n }\n\n this.errorMessage = \"\";\n return true;\n }\n\n protected async connectToNostr() {\n const seq = ++this.connectSeq;\n this.conn.set(NCStatus.Loading);\n try {\n await this.nostrService.connectToNostr(this.getRelays());\n if (seq !== this.connectSeq) return; // stale attempt\n this.conn.set(NCStatus.Ready);\n this.nostrReadyResolve?.();\n try {\n this.onNostrRelaysConnected();\n } catch (hookError) {\n console.error('Error in onNostrRelaysConnected hook:', hookError);\n }\n } catch (error) {\n if (seq !== this.connectSeq) return; // stale attempt\n console.error('Failed to connect to Nostr relays:', error);\n this.conn.set(NCStatus.Error, 'Failed to connect to relays');\n this.nostrReadyReject?.(error);\n }\n }\n\n protected ensureNostrConnected(): Promise<void> {\n return this.nostrReady;\n }\n\n protected getRelays() {\n return parseRelays(this.getAttribute('relays'));\n }\n\n protected getTheme() {\n this.theme = parseTheme(this.getAttribute('data-theme'));\n }\n\n /**\n * Delegate events within shadow DOM.\n * Example: this.delegateEvent('click', '#npub-copy', (e) => this.copyNpub(e));\n */\n protected delegateEvent<K extends keyof HTMLElementEventMap>(\n type: K,\n selector: string,\n handler: (event: HTMLElementEventMap[K]) => void\n ) {\n const root = this.shadowRoot;\n if (!root) return;\n\n // Attach once per (event type, selector)\n const key = `${type}:${selector}`;\n if ((this as any)._delegated?.has(key)) return;\n\n if (!(this as any)._delegated) (this as any)._delegated = new Set<string>();\n (this as any)._delegated.add(key);\n\n root.addEventListener(type, (e) => {\n const t = e.target as HTMLElement;\n if (t.closest(selector)) {\n handler(e as any);\n }\n });\n }\n\n protected addDelegatedListener<K extends keyof HTMLElementEventMap>(\n type: K,\n selector: string,\n handler: (event: HTMLElementEventMap[K]) => void\n ) {\n this.delegateEvent(type, selector, handler);\n }\n\n protected renderError(errorMessage: string): string {\n return `Error: ${errorMessage}`;\n }\n\n /**\n * Updates host element classes based on component status\n * This is a common pattern used by all components\n */\n protected updateHostClasses() {\n const isLoading = this.computeOverall() === NCStatus.Loading;\n const isError = this.computeOverall() === NCStatus.Error;\n const isReady = this.computeOverall() === NCStatus.Ready;\n \n // Remove all state classes\n this.classList.remove('is-clickable', 'is-disabled', 'is-error');\n \n // Add appropriate state class\n if (isLoading) {\n this.classList.add('is-disabled');\n } else if (isError) {\n this.classList.add('is-error');\n } else if (isReady) {\n this.classList.add('is-clickable');\n }\n }\n\n /**\n * Base render method that handles common render logic\n * Subclasses should override renderContent() instead of render()\n */\n protected render() {\n this.updateHostClasses();\n this.renderContent();\n }\n\n /**\n * Handles click events with njump.me default action\n * Creates custom event, dispatches it, and opens njump.me if not prevented\n */\n protected handleNjumpClick(\n eventType: string,\n detail: any,\n njumpPath: string\n ): void {\n if (this.computeOverall() !== NCStatus.Ready) return;\n \n const event = new CustomEvent(eventType, {\n detail,\n bubbles: true,\n composed: true,\n cancelable: true,\n });\n \n const notPrevented = this.dispatchEvent(event);\n if (notPrevented) {\n window.open(`https://njump.me/${njumpPath}`, '_blank', 'noopener,noreferrer');\n }\n }\n\n /**\n * Abstract method for component-specific rendering\n * Must be implemented by subclasses\n */\n protected abstract renderContent(): void;\n\n /** Private methods */\n private resetNostrReadyBarrier() {\n this.connectSeq++;\n this.nostrReady = new Promise<void>((resolve, reject) => {\n this.nostrReadyResolve = resolve;\n this.nostrReadyReject = reject;\n });\n }\n}\n","// SPDX-License-Identifier: MIT\n\n/**\n * Base Styles Utility for Nostr Components\n * =========================================\n * \n * This utility provides common base styles that can be shared across components.\n * It includes design tokens, common patterns, and utility functions.\n */\n\n/**\n * Generates minimal base styles for any Nostr component\n * Includes only essential design tokens and base component styles\n * Uses generic CSS variables that can be overridden by data-theme\n */\nexport function getBaseStyles(): string {\n return `\n :host {\n /* === GENERIC DESIGN TOKENS === */\n --nostrc-color-background: #ffffff;\n --nostrc-color-hover-background: rgba(0, 0, 0, 0.05);\n --nostrc-color-border: #e0e0e0;\n --nostrc-color-error-background: #ffebee;\n --nostrc-color-error-text: #d32f2f;\n --nostrc-color-error-icon: #d32f2f;\n \n /* === TYPOGRAPHY === */\n --nostrc-font-family-primary: ui-sans-serif, system-ui, sans-serif;\n --nostrc-font-family-mono: monospace;\n --nostrc-font-size-base: 1em;\n --nostrc-font-size-small: 0.8em;\n --nostrc-font-size-large: 1.2em;\n --nostrc-font-weight-normal: 400;\n --nostrc-font-weight-medium: 500;\n --nostrc-font-weight-bold: 700;\n \n /* === SPACING === */\n --nostrc-spacing-xs: 4px;\n --nostrc-spacing-sm: 8px;\n --nostrc-spacing-md: 12px;\n --nostrc-spacing-lg: 16px;\n --nostrc-spacing-xl: 20px;\n \n /* === BORDERS === */\n --nostrc-border-radius-sm: 4px;\n --nostrc-border-radius-md: 8px;\n --nostrc-border-radius-lg: 12px;\n --nostrc-border-radius-full: 50%;\n --nostrc-border-width: 1px;\n \n /* === SKELETON === */\n --nostrc-skeleton-color-min: #f0f0f0;\n --nostrc-skeleton-color-max: #e0e0e0;\n --nostrc-skeleton-duration: 1.5s;\n --nostrc-skeleton-timing-function: linear;\n --nostrc-skeleton-iteration-count: infinite;\n \n /* === TRANSITIONS === */\n --nostrc-transition-duration: 0.2s;\n --nostrc-transition-timing: ease;\n }\n \n :host(.is-disabled) {\n opacity: 0.7;\n cursor: not-allowed;\n }\n \n /* === ESSENTIAL UTILITY STYLES === */\n ${styleUtils.skeleton()}\n ${styleUtils.copyButton()}\n ${styleUtils.textRow()}\n ${styleUtils.profileName()}\n ${styleUtils.errorIcon()}\n `;\n}\n\n/**\n * Generates component-specific styles by combining base styles with custom styles\n * Uses CSS theme variables instead of theme prop\n */\nexport function getComponentStyles(customStyles: string): string {\n return `\n <style>\n ${getBaseStyles()}\n /* === COMPONENT-SPECIFIC STYLES === */\n ${customStyles}\n </style>\n `;\n}\n\nexport const styleUtils = {\n /**\n * Generates error state styles\n */\n error: () => `\n :host(.is-error) {\n color: var(--nostrc-color-error-text);\n }\n `,\n \n /**\n * Generates skeleton loading styles\n */\n skeleton: () => `\n .skeleton {\n background: linear-gradient(\n 90deg,\n var(--nostrc-skeleton-color-min) 0%,\n var(--nostrc-skeleton-color-max) 50%,\n var(--nostrc-skeleton-color-min) 100%\n );\n background-size: 200% 100%;\n animation: skeleton-loading var(--nostrc-skeleton-duration) var(--nostrc-skeleton-timing-function) var(--nostrc-skeleton-iteration-count);\n border-radius: var(--nostrc-border-radius-sm);\n height: 16px;\n margin-bottom: var(--nostrc-spacing-xs);\n }\n \n .skeleton:last-child {\n margin-bottom: 0;\n }\n \n @keyframes skeleton-loading {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .skeleton { animation: none; }\n }\n `,\n \n /**\n * Generates copy button styles\n */\n copyButton: () => `\n .nc-copy-btn {\n cursor: pointer;\n opacity: 0.7;\n transition: opacity var(--nostrc-transition-duration) var(--nostrc-transition-timing);\n font-size: 1.5em;\n border: none;\n background: transparent;\n color: var(--nostrc-color-text-muted);\n }\n \n .nc-copy-btn:hover {\n opacity: 1;\n }\n \n .nc-copy-btn.copied {\n color: var(--nostrc-color-accent);\n }\n `,\n \n /**\n * Generates profile name styles\n */\n profileName: () => `\n .nostr-profile-name {\n color: var(--nostrc-theme-text-primary, #333333);\n font-weight: var(--nostrc-font-weight-bold);\n padding-bottom: var(--nostrc-spacing-xs);\n }\n `,\n \n /**\n * Generates text row styles\n */\n textRow: () => `\n .text-row {\n display: flex;\n align-items: center;\n gap: var(--nostrc-spacing-sm);\n font-size: var(--nostrc-font-size-base);\n }\n \n .text-row.mono {\n font-family: var(--nostrc-font-family-mono);\n font-size: var(--nostrc-font-size-small);\n }\n `,\n \n /**\n * Generates error icon styles\n */\n errorIcon: () => `\n .error-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: var(--nostrc-border-radius-full);\n background-color: var(--nostrc-color-error-background);\n color: var(--nostrc-color-error-icon);\n font-size: 2em;\n margin: auto;\n }\n `,\n \n};\n"],"names":["decodeNpub","npub","decoded","nip19","error","hexToNpub","hex","isValidHex","maskNPub","npubString","length","npubLength","validateNpub","result","i","suffix","getPostStats","ndk","postId","reposts","NDKKind","isDirectRepost","repost","tag","isDirectReply","reply","repostsCount","likes","zapAmount","replies","replyCount","MILLISATS_PER_SAT","parseRelays","relaysAttr","list","r","isValidRelayUrl","DEFAULT_RELAYS","parseTheme","themeAttr","theme","parseBooleanAttribute","attr","escapeHtml","text","div","isValidUrl","url","parsed","u","type","validateNip05","nip05","validateBech32OfType","input","expected","validateNoteId","noteId","validateEventId","eventId","copyToClipboard","formatRelativeTime","ts","now","messageTime","diffMs","diffSec","mins","hours","days","months","years","NCStatus","NCStatus2","EVT_STATUS","NostrBaseComponent","shadow","__publicField","NostrService","name","oldValue","newValue","key","next","prev","perKeyAttr","perKeyVal","overall","overallVal","_overall","vals","status","opts","e","relays","tagName","invalidRelays","relay","invalidRelaysList","seq","_a","hookError","_b","selector","handler","root","errorMessage","isLoading","isError","isReady","eventType","detail","njumpPath","event","resolve","reject","getBaseStyles","styleUtils","getComponentStyles","customStyles"],"mappings":"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"}
1
+ {"version":3,"file":"base-styles-BSEzBDsk.js","sources":["../../src/common/utils.ts","../../src/base/base-component/nostr-base-component.ts","../../src/common/base-styles.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport NDK, { NDKKind, NDKEvent } from '@nostr-dev-kit/ndk';\nimport { nip19 } from \"nostr-tools\";\n\n\nimport { Theme } from './types';\nimport { DEFAULT_RELAYS, MILLISATS_PER_SAT } from './constants';\n\nexport const decodeNpub = (npub: string): string => {\n if (typeof npub !== 'string' || !npub.startsWith('npub1')) {\n return '';\n }\n\n try {\n const decoded = nip19.decode(npub);\n if (decoded && typeof decoded.data === 'string') {\n return decoded.data;\n }\n } catch (error) {\n console.error('Failed to decode npub:', error);\n }\n \n return '';\n};\n\n/**\n * Convert hex pubkey to npub\n */\nexport function hexToNpub(hex: string): string {\n if (!hex || !isValidHex(hex)) return '';\n try {\n return nip19.npubEncode(hex.toLowerCase());\n } catch (error) {\n console.error('Failed to encode hex to npub:', error);\n return '';\n }\n}\n\n// Could be npub, note1, naddr, nsec, etc.,\nexport const decodeNip19Entity = (entity: string): any => {\n if (typeof entity !== 'string' || !/^[a-z0-9]+1[ac-hj-np-z02-9]+/.test(entity)) {\n return null;\n }\n\n try {\n const decoded = nip19.decode(entity);\n return decoded?.data ?? null;\n } catch (error) {\n console.error('Failed to decode NIP-19 entity:', error);\n return null;\n }\n};\n\nexport function maskNPub(npubString: string = '', length = 3) {\n const npubLength = npubString.length;\n\n if (!npubString.startsWith('npub1')) {\n return 'Invalid nPub: expected npub1...';\n }\n\n if (!validateNpub(npubString)) {\n return 'Invalid nPub';\n }\n\n let result = 'npub1';\n\n for (let i = 5; i < length + 5; i++) {\n result += npubString[i];\n }\n\n result += '...';\n\n let suffix = '';\n for (let i = npubLength - 1; i >= npubLength - length; i--) {\n suffix = npubString[i] + suffix;\n }\n\n result += suffix;\n\n return result;\n}\n\nexport type Stats = {\n likes: number;\n reposts: number;\n zaps: number;\n replies: number;\n};\n\nexport async function getPostStats(ndk: NDK, postId: string): Promise<Stats> {\n const reposts = await ndk.fetchEvents({\n kinds: [NDKKind.Repost],\n '#e': [postId || ''],\n });\n\n const isDirectRepost = (repost: NDKEvent): boolean => {\n const pTagCounts = repost.tags.filter(tag => tag[0] === 'p').length;\n return pTagCounts === 1;\n };\n\n const isDirectReply = (reply: NDKEvent): boolean => {\n const eTagsCount = reply.tags.filter(tag => tag[0] === 'e').length;\n return eTagsCount === 1;\n };\n\n // Only take the count of direct reposts\n const repostsCount = Array.from(reposts).filter(isDirectRepost).length;\n\n const likes = await ndk.fetchEvents({\n kinds: [NDKKind.Reaction],\n '#e': [postId || ''],\n });\n\n // TODO: Add zap receipt validation - https://github.com/nostr-protocol/nips/blob/master/57.md#appendix-f-validating-zap-receipts\n // const zaps = await ndk.fetchEvents({\n // kinds: [NDKKind.Zap],\n // '#e': [postId || '']\n // });\n\n // const zapAmount = Array.from(zaps).reduce((prev, curr) => {\n // const bolt11Tag = curr.getMatchingTags('bolt11');\n\n // if(\n // !bolt11Tag ||\n // !Array.isArray(bolt11Tag) ||\n // bolt11Tag.length === 0 ||\n // !bolt11Tag[0] ||\n // !Array.isArray(bolt11Tag[0]) ||\n // (bolt11Tag[0] as string[]).length === 0\n // ) {\n // return prev;\n // }\n\n // const bolt11 = bolt11Tag[0][1];\n\n // const decodedbol11 = decode(bolt11);\n\n // const amountSection = decodedbol11.sections.find(section => section.name === 'amount');\n\n // if(amountSection) {\n // const millisats = Number(amountSection.value);\n\n // return prev + millisats;\n // }\n\n // return prev;\n // }, 0);\n\n const zapAmount = 0;\n\n const replies = await ndk.fetchEvents({\n kinds: [NDKKind.Text],\n '#e': [postId || ''],\n });\n\n // Only take the direct replies\n // https://github.com/nostr-protocol/nips/blob/master/10.md#positional-e-tags-deprecated\n const replyCount = Array.from(replies).filter(isDirectReply).length;\n\n return {\n likes: likes.size,\n reposts: repostsCount,\n zaps: zapAmount / MILLISATS_PER_SAT,\n replies: replyCount,\n };\n}\n\nexport function parseRelays(relaysAttr: string | null): string[] {\n if (relaysAttr) {\n const list = relaysAttr\n .split(',')\n .map(r => r.trim())\n .filter(Boolean)\n .filter(isValidRelayUrl);\n // fall back to defaults if user provided no valid entries\n return list.length ? Array.from(new Set(list)) : [...DEFAULT_RELAYS];\n }\n return [...DEFAULT_RELAYS];\n}\n\nexport function parseTheme(themeAttr: string | null): Theme {\n\n const theme = themeAttr?.trim().toLowerCase();\n\n if (theme === 'light' || theme === 'dark') {\n return theme;\n }\n\n return 'light';\n}\n\nexport function parseBooleanAttribute(attr: string | null): boolean {\n // Handles: \"true\", \"\", null, \"false\"\n if (attr === null) return false;\n if (attr === '' || attr.toLowerCase() === 'true') return true;\n return false;\n}\n\nexport function escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n\nexport function isValidUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return ['http:', 'https:'].includes(parsed.protocol);\n } catch {\n return false;\n }\n}\n\nexport function isValidRelayUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return u.protocol === 'wss:' || u.protocol === 'ws:';\n } catch {\n return false;\n }\n}\n\nexport function isValidHex(hex: string): boolean {\n return /^[0-9a-fA-F]+$/.test(hex) && hex.length === 64;\n}\n\nexport function validateNpub(npub: string): boolean {\n try {\n const { type } = nip19.decode(npub);\n return type === 'npub';\n } catch (e) {\n return false;\n }\n}\n\nexport function validateNip05(nip05: string): boolean {\n const nip05Regex = /^[a-zA-Z0-9_\\-\\.]+@[a-zA-Z0-9_\\-\\.]+\\.[a-zA-Z]{2,}$/;\n return nip05Regex.test(nip05);\n}\n\nfunction validateBech32OfType(input: string, expected: 'note' | 'nevent'): boolean {\n try {\n const { type } = nip19.decode(input);\n return type === expected;\n } catch {\n return false;\n }\n}\n\nexport function validateNoteId(noteId: string): boolean {\n return validateBech32OfType(noteId, 'note');\n}\n\nexport function validateEventId(eventId: string): boolean {\n return validateBech32OfType(eventId, 'nevent');\n}\n\nexport function copyToClipboard(text: string): Promise<void> {\n return navigator.clipboard.writeText(text)\n}\n\n/**\n * Format timestamp as relative time (e.g., \"2 mins ago\", \"1 month ago\")\n * @param ts Timestamp in seconds\n * @returns Formatted relative time string\n */\nexport function formatRelativeTime(ts: number): string {\n try {\n const now = Date.now();\n const messageTime = ts * 1000;\n const diffMs = now - messageTime;\n\n // Convert to seconds\n const diffSec = Math.floor(diffMs / 1000);\n\n if (diffSec < 60) {\n return 'just now';\n }\n\n // Minutes\n if (diffSec < 3600) {\n const mins = Math.floor(diffSec / 60);\n return `${mins} ${mins === 1 ? 'min' : 'mins'} ago`;\n }\n\n // Hours\n if (diffSec < 86400) {\n const hours = Math.floor(diffSec / 3600);\n return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;\n }\n\n // Days\n if (diffSec < 2592000) { // ~30 days\n const days = Math.floor(diffSec / 86400);\n return `${days} ${days === 1 ? 'day' : 'days'} ago`;\n }\n\n // Months\n if (diffSec < 31536000) { // ~365 days\n const months = Math.floor(diffSec / 2592000);\n return `${months} ${months === 1 ? 'month' : 'months'} ago`;\n }\n\n // Years\n const years = Math.floor(diffSec / 31536000);\n return `${years} ${years === 1 ? 'year' : 'years'} ago`;\n } catch (error) {\n console.error('Error formatting relative time:', error);\n return 'unknown';\n }\n}\n","// SPDX-License-Identifier: MIT\n\nimport { NostrService } from '../../common/nostr-service';\nimport { Theme } from '../../common/types';\nimport { parseRelays, parseTheme, isValidRelayUrl } from '../../common/utils';\n\nexport enum NCStatus {\n Idle, // 0\n Loading, // 1\n Ready, // 2\n Error, // 3\n}\n\nconst EVT_STATUS = 'nc:status';\n\n/**\n * NostrBaseComponent\n * ==================\n * Foundation for all Nostr web components in this library.\n *\n * Overview\n * - Manages relay connectivity via a shared `NostrService`.\n * - Parses common attributes (`theme`, `relays`) and applies theme.\n * - Provides a generic status management logic, that is extensible by derived classes.\n * - Offers small utilities useful to subclasses (event delegation, error snippet).\n *\n * Observed attributes\n * - `data-theme` — \"light\" | \"dark\"\n * - `relays` — CSV of relay URLs\n * \n * Events\n * - `nc:status` — from base, reflects connection and user/profile loading status\n * \n * TODO: Is this class doing too much work? Time to split into smaller components?\n */\nexport abstract class NostrBaseComponent extends HTMLElement {\n\n protected nostrService: NostrService = NostrService.getInstance();\n\n protected theme: Theme = 'light';\n protected errorMessage: string = '';\n\n protected nostrReady!: Promise<void>;\n protected nostrReadyResolve?: () => void;\n protected nostrReadyReject?: (e: unknown) => void;\n\n protected conn = this.channel('connection');\n\n private _statuses = new Map<string, NCStatus>();\n private _overall: NCStatus = NCStatus.Idle;\n\n // guard to ignore stale connects\n private connectSeq = 0;\n\n constructor(shadow: boolean = true) {\n super();\n if (shadow) this.attachShadow({ mode: 'open' });\n this.resetNostrReadyBarrier();\n }\n\n /** Lifecycle methods */\n static get observedAttributes() {\n return ['data-theme', 'relays'];\n }\n\n connectedCallback() {\n if (this.validateInputs()) {\n this.getTheme();\n // Avoid duplicate connects if a subclass handles it\n if (this.conn.get() === NCStatus.Idle) {\n void this.connectToNostr();\n }\n }\n }\n\n disconnectedCallback() {\n // Clean up delegated event listeners if shadow root exists\n if (this.shadowRoot && (this as any)._delegated) {\n (this as any)._delegated.clear();\n }\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null\n ) {\n if (oldValue === newValue) return;\n\n if (name === 'data-theme' || name === 'relays') {\n if (this.validateInputs()) {\n if (name === 'relays') {\n this.resetNostrReadyBarrier();\n void this.connectToNostr();\n }\n\n if (name === 'data-theme') {\n this.getTheme();\n this.render();\n }\n }\n }\n }\n\n /** Status map API */\n\n protected setStatusFor(key: string, next: NCStatus, error?: string) {\n const prev = this._statuses.get(key);\n const changed = prev !== next || (next === NCStatus.Error && !!error);\n\n if (!changed) return;\n\n this._statuses.set(key, next);\n\n if (next === NCStatus.Error && error) {\n this.errorMessage = error;\n } else if (prev === NCStatus.Error && next !== NCStatus.Error) {\n this.errorMessage = '';\n }\n\n // Reflect per-key attribute, e.g. user-status=\"loading\"\n const perKeyAttr = `${key}-status`;\n const perKeyVal = NCStatus[next].toLowerCase();\n if (this.getAttribute(perKeyAttr) !== perKeyVal) {\n this.setAttribute(perKeyAttr, perKeyVal);\n }\n\n // Compute & reflect overall for backward-compat CSS\n const overall = this.computeOverall();\n const overallVal = NCStatus[overall].toLowerCase();\n if (this._overall !== overall) {\n this._overall = overall;\n this.setAttribute('status', overallVal);\n this.onStatusChange(overall);\n } else if (overall === NCStatus.Error && error) {\n // propagate error updates even if overall state didn't flip\n this.onStatusChange(overall);\n }\n\n // Emit a single event with structured detail\n this.dispatchEvent(new CustomEvent(EVT_STATUS, {\n detail: {\n key,\n status: next,\n all: this.snapshotStatuses(),\n overall: this._overall,\n errorMessage: this.errorMessage || undefined,\n },\n bubbles: true,\n composed: true,\n }));\n }\n\n protected getStatusFor(key: string): NCStatus {\n return this._statuses.get(key) ?? NCStatus.Idle;\n }\n\n protected snapshotStatuses(): Record<string, NCStatus> {\n return Object.fromEntries(this._statuses.entries());\n }\n\n /** Overall status change hook */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected onStatusChange(_overall: NCStatus) { }\n\n /** Hook for subclasses to react when relays are connected */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected onNostrRelaysConnected() { }\n\n protected computeOverall(): NCStatus {\n const vals = [...this._statuses.values()];\n if (vals.includes(NCStatus.Error)) return NCStatus.Error;\n if (vals.includes(NCStatus.Loading)) return NCStatus.Loading;\n if (vals.includes(NCStatus.Ready)) return NCStatus.Ready;\n return NCStatus.Idle;\n }\n\n /**\n * Before first channel.set(), the map won't be set and hence get() will return `idle`.\n * In some cases (or maybe in all cases), this is not ideal.\n * In UserComponent, 'user' not set and hence returning `idle`, while\n * 'connect' switched between idle -> loading -> ready\n * and then\n * 'user' switches to loading -> ready,\n * results in loading -> ready -> loading -> ready in profile-badge onStatusChange.\n * \n * To avoid this, this function is called from UserComponent constructor,\n * to set default value as 'loading' without emitting onStatusChange event.\n * \n * Makes sense?\n * Try commenting this function call in UserComponent()\n * and add a log in ProfileBadge :: onStatusChange. You will get it.\n */\n protected initChannelStatus(key: string, status: NCStatus, opts = { reflectOverall: false }) {\n this._statuses.set(key, status);\n this.setAttribute(`${key}-status`, NCStatus[status].toLowerCase());\n if (opts.reflectOverall) {\n const overall = this.computeOverall();\n this._overall = overall;\n this.setAttribute('status', NCStatus[overall].toLowerCase());\n }\n }\n\n protected channel(key: string) {\n return {\n set: (s: NCStatus, e?: string) => this.setStatusFor(key, s, e),\n get: () => this.getStatusFor(key)\n };\n }\n\n /** Protected methods */\n protected validateInputs(): boolean {\n const theme = this.getAttribute('data-theme') || 'light';\n const relays = this.getAttribute('relays');\n const tagName = this.tagName.toLowerCase();\n\n if (!(theme === 'light' || theme === 'dark')) {\n this.conn.set(NCStatus.Error, `Invalid theme '${theme}'. Accepted values are 'light', 'dark'`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n } else if (relays && typeof relays != 'string') {\n this.conn.set(NCStatus.Error, `Invalid relays list`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n } else if (relays) {\n const relayList = relays.split(',').map(r => r.trim()).filter(Boolean);\n const invalidRelays = relayList.filter(relay => !isValidRelayUrl(relay));\n \n if (invalidRelays.length > 0) {\n const invalidRelaysList = invalidRelays.join(', ');\n this.conn.set(NCStatus.Error, `Invalid relay URLs: ${invalidRelaysList}. Relay URLs must start with 'wss://' or 'ws://'`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n }\n }\n\n this.errorMessage = \"\";\n return true;\n }\n\n protected async connectToNostr() {\n const seq = ++this.connectSeq;\n this.conn.set(NCStatus.Loading);\n try {\n await this.nostrService.connectToNostr(this.getRelays());\n if (seq !== this.connectSeq) return; // stale attempt\n this.conn.set(NCStatus.Ready);\n this.nostrReadyResolve?.();\n try {\n this.onNostrRelaysConnected();\n } catch (hookError) {\n console.error('Error in onNostrRelaysConnected hook:', hookError);\n }\n } catch (error) {\n if (seq !== this.connectSeq) return; // stale attempt\n console.error('Failed to connect to Nostr relays:', error);\n this.conn.set(NCStatus.Error, 'Failed to connect to relays');\n this.nostrReadyReject?.(error);\n }\n }\n\n protected ensureNostrConnected(): Promise<void> {\n return this.nostrReady;\n }\n\n protected getRelays() {\n return parseRelays(this.getAttribute('relays'));\n }\n\n protected getTheme() {\n this.theme = parseTheme(this.getAttribute('data-theme'));\n }\n\n /**\n * Delegate events within shadow DOM.\n * Example: this.delegateEvent('click', '#npub-copy', (e) => this.copyNpub(e));\n */\n protected delegateEvent<K extends keyof HTMLElementEventMap>(\n type: K,\n selector: string,\n handler: (event: HTMLElementEventMap[K]) => void\n ) {\n const root = this.shadowRoot;\n if (!root) return;\n\n // Attach once per (event type, selector)\n const key = `${type}:${selector}`;\n if ((this as any)._delegated?.has(key)) return;\n\n if (!(this as any)._delegated) (this as any)._delegated = new Set<string>();\n (this as any)._delegated.add(key);\n\n root.addEventListener(type, (e) => {\n const t = e.target as HTMLElement;\n if (t.closest(selector)) {\n handler(e as any);\n }\n });\n }\n\n protected addDelegatedListener<K extends keyof HTMLElementEventMap>(\n type: K,\n selector: string,\n handler: (event: HTMLElementEventMap[K]) => void\n ) {\n this.delegateEvent(type, selector, handler);\n }\n\n protected renderError(errorMessage: string): string {\n return `Error: ${errorMessage}`;\n }\n\n /**\n * Updates host element classes based on component status\n * This is a common pattern used by all components\n */\n protected updateHostClasses() {\n const isLoading = this.computeOverall() === NCStatus.Loading;\n const isError = this.computeOverall() === NCStatus.Error;\n const isReady = this.computeOverall() === NCStatus.Ready;\n \n // Remove all state classes\n this.classList.remove('is-clickable', 'is-disabled', 'is-error');\n \n // Add appropriate state class\n if (isLoading) {\n this.classList.add('is-disabled');\n } else if (isError) {\n this.classList.add('is-error');\n } else if (isReady) {\n this.classList.add('is-clickable');\n }\n }\n\n /**\n * Base render method that handles common render logic\n * Subclasses should override renderContent() instead of render()\n */\n protected render() {\n this.updateHostClasses();\n this.renderContent();\n }\n\n /**\n * Handles click events with njump.me default action\n * Creates custom event, dispatches it, and opens njump.me if not prevented\n */\n protected handleNjumpClick(\n eventType: string,\n detail: any,\n njumpPath: string\n ): void {\n if (this.computeOverall() !== NCStatus.Ready) return;\n \n const event = new CustomEvent(eventType, {\n detail,\n bubbles: true,\n composed: true,\n cancelable: true,\n });\n \n const notPrevented = this.dispatchEvent(event);\n if (notPrevented) {\n window.open(`https://njump.me/${njumpPath}`, '_blank', 'noopener,noreferrer');\n }\n }\n\n /**\n * Abstract method for component-specific rendering\n * Must be implemented by subclasses\n */\n protected abstract renderContent(): void;\n\n /** Private methods */\n private resetNostrReadyBarrier() {\n this.connectSeq++;\n this.nostrReady = new Promise<void>((resolve, reject) => {\n this.nostrReadyResolve = resolve;\n this.nostrReadyReject = reject;\n });\n }\n}\n","// SPDX-License-Identifier: MIT\n\n/**\n * Base Styles Utility for Nostr Components\n * =========================================\n * \n * This utility provides common base styles that can be shared across components.\n * It includes design tokens, common patterns, and utility functions.\n */\n\n/**\n * Generates minimal base styles for any Nostr component\n * Includes only essential design tokens and base component styles\n * Uses generic CSS variables that can be overridden by data-theme\n */\nexport function getBaseStyles(): string {\n return `\n :host {\n /* === GENERIC DESIGN TOKENS === */\n --nostrc-color-background: #ffffff;\n --nostrc-color-hover-background: rgba(0, 0, 0, 0.05);\n --nostrc-color-border: #e0e0e0;\n --nostrc-color-error-background: #ffebee;\n --nostrc-color-error-text: #d32f2f;\n --nostrc-color-error-icon: #d32f2f;\n \n /* === TYPOGRAPHY === */\n --nostrc-font-family-primary: ui-sans-serif, system-ui, sans-serif;\n --nostrc-font-family-mono: monospace;\n --nostrc-font-size-base: 1em;\n --nostrc-font-size-small: 0.8em;\n --nostrc-font-size-large: 1.2em;\n --nostrc-font-weight-normal: 400;\n --nostrc-font-weight-medium: 500;\n --nostrc-font-weight-bold: 700;\n \n /* === SPACING === */\n --nostrc-spacing-xs: 4px;\n --nostrc-spacing-sm: 8px;\n --nostrc-spacing-md: 12px;\n --nostrc-spacing-lg: 16px;\n --nostrc-spacing-xl: 20px;\n \n /* === BORDERS === */\n --nostrc-border-radius-sm: 4px;\n --nostrc-border-radius-md: 8px;\n --nostrc-border-radius-lg: 12px;\n --nostrc-border-radius-full: 50%;\n --nostrc-border-width: 1px;\n \n /* === SKELETON === */\n --nostrc-skeleton-color-min: #f0f0f0;\n --nostrc-skeleton-color-max: #e0e0e0;\n --nostrc-skeleton-duration: 1.5s;\n --nostrc-skeleton-timing-function: linear;\n --nostrc-skeleton-iteration-count: infinite;\n \n /* === TRANSITIONS === */\n --nostrc-transition-duration: 0.2s;\n --nostrc-transition-timing: ease;\n }\n \n :host(.is-disabled) {\n opacity: 0.7;\n cursor: not-allowed;\n }\n \n /* === ESSENTIAL UTILITY STYLES === */\n ${styleUtils.skeleton()}\n ${styleUtils.copyButton()}\n ${styleUtils.textRow()}\n ${styleUtils.profileName()}\n ${styleUtils.errorIcon()}\n `;\n}\n\n/**\n * Generates component-specific styles by combining base styles with custom styles\n * Uses CSS theme variables instead of theme prop\n */\nexport function getComponentStyles(customStyles: string): string {\n return `\n <style>\n ${getBaseStyles()}\n /* === COMPONENT-SPECIFIC STYLES === */\n ${customStyles}\n </style>\n `;\n}\n\nexport const styleUtils = {\n /**\n * Generates error state styles\n */\n error: () => `\n :host(.is-error) {\n color: var(--nostrc-color-error-text);\n }\n `,\n \n /**\n * Generates skeleton loading styles\n */\n skeleton: () => `\n .skeleton {\n background: linear-gradient(\n 90deg,\n var(--nostrc-skeleton-color-min) 0%,\n var(--nostrc-skeleton-color-max) 50%,\n var(--nostrc-skeleton-color-min) 100%\n );\n background-size: 200% 100%;\n animation: skeleton-loading var(--nostrc-skeleton-duration) var(--nostrc-skeleton-timing-function) var(--nostrc-skeleton-iteration-count);\n border-radius: var(--nostrc-border-radius-sm);\n height: 16px;\n margin-bottom: var(--nostrc-spacing-xs);\n }\n \n .skeleton:last-child {\n margin-bottom: 0;\n }\n \n @keyframes skeleton-loading {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .skeleton { animation: none; }\n }\n `,\n \n /**\n * Generates copy button styles\n */\n copyButton: () => `\n .nc-copy-btn {\n cursor: pointer;\n opacity: 0.7;\n transition: opacity var(--nostrc-transition-duration) var(--nostrc-transition-timing);\n font-size: 1.5em;\n border: none;\n background: transparent;\n color: var(--nostrc-color-text-muted);\n }\n \n .nc-copy-btn:hover {\n opacity: 1;\n }\n \n .nc-copy-btn.copied {\n color: var(--nostrc-color-accent);\n }\n `,\n \n /**\n * Generates profile name styles\n */\n profileName: () => `\n .nostr-profile-name {\n color: var(--nostrc-theme-text-primary, #333333);\n font-weight: var(--nostrc-font-weight-bold);\n padding-bottom: var(--nostrc-spacing-xs);\n }\n `,\n \n /**\n * Generates text row styles\n */\n textRow: () => `\n .text-row {\n display: flex;\n align-items: center;\n gap: var(--nostrc-spacing-sm);\n font-size: var(--nostrc-font-size-base);\n }\n \n .text-row.mono {\n font-family: var(--nostrc-font-family-mono);\n font-size: var(--nostrc-font-size-small);\n }\n `,\n \n /**\n * Generates error icon styles\n */\n errorIcon: () => `\n .error-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: var(--nostrc-border-radius-full);\n background-color: var(--nostrc-color-error-background);\n color: var(--nostrc-color-error-icon);\n font-size: 2em;\n margin: auto;\n }\n `,\n \n};\n"],"names":["decodeNpub","npub","decoded","nip19","error","hexToNpub","hex","isValidHex","maskNPub","npubString","length","npubLength","validateNpub","result","i","suffix","getPostStats","ndk","postId","reposts","NDKKind","isDirectRepost","repost","tag","isDirectReply","reply","repostsCount","likes","zapAmount","replies","replyCount","MILLISATS_PER_SAT","parseRelays","relaysAttr","list","r","isValidRelayUrl","DEFAULT_RELAYS","parseTheme","themeAttr","theme","parseBooleanAttribute","attr","escapeHtml","text","div","isValidUrl","url","parsed","u","type","validateNip05","nip05","validateBech32OfType","input","expected","validateNoteId","noteId","validateEventId","eventId","copyToClipboard","formatRelativeTime","ts","now","messageTime","diffMs","diffSec","mins","hours","days","months","years","NCStatus","NCStatus2","EVT_STATUS","NostrBaseComponent","shadow","__publicField","NostrService","name","oldValue","newValue","key","next","prev","perKeyAttr","perKeyVal","overall","overallVal","_overall","vals","status","opts","e","relays","tagName","invalidRelays","relay","invalidRelaysList","seq","_a","hookError","_b","selector","handler","root","errorMessage","isLoading","isError","isReady","eventType","detail","njumpPath","event","resolve","reject","getBaseStyles","styleUtils","getComponentStyles","customStyles"],"mappings":"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"}
@@ -1,15 +1,15 @@
1
- import{e as r,m,d as f}from"./base-styles-CBypR3FR.js";function l(e){const{value:a,display:t=a,className:s="",monospace:o=!1,title:n=t,showCopyButton:c=!1}=e,i=s.replace(/[^\w\- ]/g,""),p=r(t),u=r(n),d="&#x2398;";if(c){const y=r(a);return`
2
- <div class="${`text-row nc-copy ${o?"mono":""} ${i}`.trim()}" data-copy="${y}" title="${u}">
1
+ import{e as r,m,f}from"./base-styles-BSEzBDsk.js";function l(e){const{value:a,display:t=a,className:s="",monospace:o=!1,title:n=t,showCopyButton:c=!1}=e,i=s.replace(/[^\w\- ]/g,""),p=r(t),u=r(n),y="&#x2398;";if(c){const d=r(a);return`
2
+ <div class="${`text-row nc-copy ${o?"mono":""} ${i}`.trim()}" data-copy="${d}" title="${u}">
3
3
  <span class="nc-copy-text">${p}</span>
4
4
  <button type="button"
5
5
  class="nc-copy-btn"
6
6
  aria-label="Copy"
7
7
  title="Copy"
8
- >${d}</button>
8
+ >${y}</button>
9
9
  </div>
10
10
  `}return`
11
11
  <div class="${`text-row ${o?"mono":""} ${i}`.trim()}" title="${u}">
12
12
  ${p}
13
13
  </div>
14
- `}function w(e){return l({value:e,display:m(e),monospace:!0,showCopyButton:!0,title:e})}function v(e){return l({value:e,display:e,monospace:!0,showCopyButton:!0})}function N(e){const{name:a,className:t="",title:s,showCopyButton:o=!1}=e;return l({value:a,display:a,className:`nostr-profile-name ${t}`.trim(),title:s??a,showCopyButton:o})}function g(e){e.addDelegatedListener("click",".nc-copy-btn",async a=>{var n;a.stopPropagation();const t=(n=a.target)==null?void 0:n.closest(".nc-copy-btn");if(!t)return;const s=t==null?void 0:t.closest(".nc-copy"),o=(s==null?void 0:s.dataset.copy)??"";if(o)try{await f(o),t.classList.add("copied");const c=t.getAttribute("aria-label")||"Copy";t.setAttribute("aria-label","Copied!"),setTimeout(()=>{t.classList.remove("copied"),t.setAttribute("aria-label",c)},1200)}catch{}})}export{v as a,w as b,g as c,l as d,N as r};
15
- //# sourceMappingURL=copy-delegation-C4uvRTVM.js.map
14
+ `}function w(e){return l({value:e,display:m(e),monospace:!0,showCopyButton:!0,title:e})}function v(e){return l({value:e,display:e,monospace:!0,showCopyButton:!0})}function N(e){const{name:a,className:t="",title:s,showCopyButton:o=!1}=e;return l({value:a,display:a,className:`nostr-profile-name ${t}`.trim(),title:s??a,showCopyButton:o})}function g(e){e.addDelegatedListener("click",".nc-copy-btn",async a=>{var n;a.stopPropagation();const t=(n=a.target)==null?void 0:n.closest(".nc-copy-btn");if(!t)return;const s=t==null?void 0:t.closest(".nc-copy"),o=(s==null?void 0:s.dataset.copy)??"";if(o)try{await f(o),t.classList.add("copied");const c=t.getAttribute("aria-label")||"Copy";t.setAttribute("aria-label","Copied!"),setTimeout(()=>{t.classList.remove("copied"),t.setAttribute("aria-label",c)},1200)}catch{}})}export{v as a,w as b,l as c,g as d,N as r};
15
+ //# sourceMappingURL=copy-delegation-B7y2q5Kn.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"copy-delegation-C4uvRTVM.js","sources":["../../src/base/text-row/render-text-row.ts","../../src/base/text-row/render-npub.ts","../../src/base/text-row/render-nip05.ts","../../src/base/text-row/render-name.ts","../../src/base/copy-delegation.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport { escapeHtml } from '../../common/utils';\n\nexport interface TextRowOptions {\n value: string; // the raw text to copy\n display?: string; // what to show (defaults to value)\n className?: string; // extra classes to add to the row\n monospace?: boolean; // show in mono font\n title?: string; // title/tooltip for the text\n showCopyButton?: boolean; // show copy button (default false)\n}\n\nexport function renderTextRow(opts: TextRowOptions): string {\n const {\n value,\n display = value,\n className = '',\n monospace = false,\n title = display,\n showCopyButton = false,\n } = opts;\n\n // Allow only class token chars to avoid breaking out of class attr\n const safeClassName = className.replace(/[^\\w\\- ]/g, '');\n const safeDisplay = escapeHtml(display);\n const safeTitle = escapeHtml(title);\n const iconHtml = '&#x2398;'; // ⎘\n\n if (showCopyButton) {\n const safeValue = escapeHtml(value);\n const rowClass = `text-row nc-copy ${monospace ? 'mono' : ''} ${safeClassName}`.trim();\n return `\n <div class=\"${rowClass}\" data-copy=\"${safeValue}\" title=\"${safeTitle}\">\n <span class=\"nc-copy-text\">${safeDisplay}</span>\n <button type=\"button\" \n class=\"nc-copy-btn\"\n aria-label=\"Copy\"\n title=\"Copy\"\n >${iconHtml}</button>\n </div>\n `;\n }\n\n const rowClass = `text-row ${monospace ? 'mono' : ''} ${safeClassName}`.trim();\n return `\n <div class=\"${rowClass}\" title=\"${safeTitle}\">\n ${safeDisplay}\n </div>\n `;\n}\n","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from \"./render-text-row\";\nimport { maskNPub } from \"../../common/utils\";\n\nexport function renderNpub(\n npub: string,\n): string {\n\n return renderTextRow({\n value: npub,\n display: maskNPub(npub),\n monospace: true,\n showCopyButton: true,\n title: npub,\n });\n}","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from \"./render-text-row\";\n\nexport function renderNip05(\n nip05: string,\n): string {\n return renderTextRow({\n value: nip05,\n display: nip05,\n monospace: true,\n showCopyButton: true,\n });\n}","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from './render-text-row';\n\nexport interface RenderNameOptions {\n name: string;\n className?: string;\n title?: string;\n showCopyButton?: boolean;\n}\n\nexport function renderName(options: RenderNameOptions): string {\n const { name, className = '', title, showCopyButton = false } = options;\n \n return renderTextRow({\n value: name,\n display: name,\n className: `nostr-profile-name ${className}`.trim(),\n title: title ?? name,\n showCopyButton: showCopyButton,\n });\n}\n","// SPDX-License-Identifier: MIT\n\nimport { copyToClipboard } from '../common/utils';\n\ninterface DelegatedHost {\n addDelegatedListener(\n type: string,\n selector: string,\n handler: (e: Event) => void\n ): void;\n }\n\nexport function attachCopyDelegation(host: DelegatedHost) {\n host.addDelegatedListener('click', '.nc-copy-btn', async (e: Event) => {\n e.stopPropagation();\n const btn = (e.target as HTMLElement)?.closest('.nc-copy-btn') as HTMLElement;\n if (!btn) return;\n const row = btn?.closest('.nc-copy') as HTMLElement | null;\n const value = row?.dataset.copy ?? '';\n if (!value) return;\n\n try {\n await copyToClipboard(value);\n btn.classList.add('copied');\n const prev = btn.getAttribute('aria-label') || 'Copy';\n btn.setAttribute('aria-label', 'Copied!');\n setTimeout(() => {\n btn.classList.remove('copied');\n btn.setAttribute('aria-label', prev);\n }, 1200);\n } catch {\n // optional: surface error // todo\n }\n });\n}\n"],"names":["renderTextRow","opts","value","display","className","monospace","title","showCopyButton","safeClassName","safeDisplay","escapeHtml","safeTitle","iconHtml","safeValue","renderNpub","npub","maskNPub","renderNip05","nip05","renderName","options","name","attachCopyDelegation","host","e","btn","_a","row","copyToClipboard","prev"],"mappings":"uDAaO,SAASA,EAAcC,EAA8B,CACpD,KAAA,CACJ,MAAAC,EACA,QAAAC,EAAUD,EACV,UAAAE,EAAY,GACZ,UAAAC,EAAY,GACZ,MAAAC,EAAQH,EACR,eAAAI,EAAiB,EAAA,EACfN,EAGEO,EAAgBJ,EAAU,QAAQ,YAAa,EAAE,EACjDK,EAAcC,EAAWP,CAAO,EAChCQ,EAAcD,EAAWJ,CAAK,EAC9BM,EAAc,WAEpB,GAAIL,EAAgB,CACZ,MAAAM,EAAYH,EAAWR,CAAK,EAE3B,MAAA;AAAA,oBADU,oBAAoBG,EAAY,OAAS,EAAE,IAAIG,CAAa,GAAG,KAAK,CAE7D,gBAAgBK,CAAS,YAAYF,CAAS;AAAA,qCACrCF,CAAW;AAAA;AAAA;AAAA;AAAA;AAAA,iBAK/BG,CAAQ;AAAA;AAAA,KAAA,CAMhB,MAAA;AAAA,kBADU,YAAYP,EAAY,OAAS,EAAE,IAAIG,CAAa,GAAG,KAAK,CAErD,YAAYG,CAAS;AAAA,QACvCF,CAAW;AAAA;AAAA,GAGnB,CC7CO,SAASK,EACdC,EACQ,CAER,OAAOf,EAAc,CACnB,MAAOe,EACP,QAASC,EAASD,CAAI,EACtB,UAAW,GACX,eAAgB,GAChB,MAAOA,CAAA,CACR,CACH,CCZO,SAASE,EACdC,EACQ,CACR,OAAOlB,EAAc,CACnB,MAAOkB,EACP,QAASA,EACT,UAAW,GACX,eAAgB,EAAA,CACjB,CACH,CCFO,SAASC,EAAWC,EAAoC,CAC7D,KAAM,CAAE,KAAAC,EAAM,UAAAjB,EAAY,GAAI,MAAAE,EAAO,eAAAC,EAAiB,IAAUa,EAEhE,OAAOpB,EAAc,CACnB,MAAOqB,EACP,QAASA,EACT,UAAW,sBAAsBjB,CAAS,GAAG,KAAK,EAClD,MAAOE,GAASe,EAChB,eAAAd,CAAA,CACD,CACH,CCTO,SAASe,EAAqBC,EAAqB,CACxDA,EAAK,qBAAqB,QAAS,eAAgB,MAAOC,GAAa,OACrEA,EAAE,gBAAgB,EAClB,MAAMC,GAAOC,EAAAF,EAAE,SAAF,YAAAE,EAA0B,QAAQ,gBAC/C,GAAI,CAACD,EAAK,OACJ,MAAAE,EAAMF,GAAA,YAAAA,EAAK,QAAQ,YACnBvB,GAAQyB,GAAA,YAAAA,EAAK,QAAQ,OAAQ,GACnC,GAAKzB,EAED,GAAA,CACF,MAAM0B,EAAgB1B,CAAK,EACvBuB,EAAA,UAAU,IAAI,QAAQ,EAC1B,MAAMI,EAAOJ,EAAI,aAAa,YAAY,GAAK,OAC3CA,EAAA,aAAa,aAAc,SAAS,EACxC,WAAW,IAAM,CACXA,EAAA,UAAU,OAAO,QAAQ,EACzBA,EAAA,aAAa,aAAcI,CAAI,GAClC,IAAI,CAAA,MACD,CAAA,CAER,CACD,CACH"}
1
+ {"version":3,"file":"copy-delegation-B7y2q5Kn.js","sources":["../../src/base/text-row/render-text-row.ts","../../src/base/text-row/render-npub.ts","../../src/base/text-row/render-nip05.ts","../../src/base/text-row/render-name.ts","../../src/base/copy-delegation.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport { escapeHtml } from '../../common/utils';\n\nexport interface TextRowOptions {\n value: string; // the raw text to copy\n display?: string; // what to show (defaults to value)\n className?: string; // extra classes to add to the row\n monospace?: boolean; // show in mono font\n title?: string; // title/tooltip for the text\n showCopyButton?: boolean; // show copy button (default false)\n}\n\nexport function renderTextRow(opts: TextRowOptions): string {\n const {\n value,\n display = value,\n className = '',\n monospace = false,\n title = display,\n showCopyButton = false,\n } = opts;\n\n // Allow only class token chars to avoid breaking out of class attr\n const safeClassName = className.replace(/[^\\w\\- ]/g, '');\n const safeDisplay = escapeHtml(display);\n const safeTitle = escapeHtml(title);\n const iconHtml = '&#x2398;'; // ⎘\n\n if (showCopyButton) {\n const safeValue = escapeHtml(value);\n const rowClass = `text-row nc-copy ${monospace ? 'mono' : ''} ${safeClassName}`.trim();\n return `\n <div class=\"${rowClass}\" data-copy=\"${safeValue}\" title=\"${safeTitle}\">\n <span class=\"nc-copy-text\">${safeDisplay}</span>\n <button type=\"button\" \n class=\"nc-copy-btn\"\n aria-label=\"Copy\"\n title=\"Copy\"\n >${iconHtml}</button>\n </div>\n `;\n }\n\n const rowClass = `text-row ${monospace ? 'mono' : ''} ${safeClassName}`.trim();\n return `\n <div class=\"${rowClass}\" title=\"${safeTitle}\">\n ${safeDisplay}\n </div>\n `;\n}\n","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from \"./render-text-row\";\nimport { maskNPub } from \"../../common/utils\";\n\nexport function renderNpub(\n npub: string,\n): string {\n\n return renderTextRow({\n value: npub,\n display: maskNPub(npub),\n monospace: true,\n showCopyButton: true,\n title: npub,\n });\n}","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from \"./render-text-row\";\n\nexport function renderNip05(\n nip05: string,\n): string {\n return renderTextRow({\n value: nip05,\n display: nip05,\n monospace: true,\n showCopyButton: true,\n });\n}","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from './render-text-row';\n\nexport interface RenderNameOptions {\n name: string;\n className?: string;\n title?: string;\n showCopyButton?: boolean;\n}\n\nexport function renderName(options: RenderNameOptions): string {\n const { name, className = '', title, showCopyButton = false } = options;\n \n return renderTextRow({\n value: name,\n display: name,\n className: `nostr-profile-name ${className}`.trim(),\n title: title ?? name,\n showCopyButton: showCopyButton,\n });\n}\n","// SPDX-License-Identifier: MIT\n\nimport { copyToClipboard } from '../common/utils';\n\ninterface DelegatedHost {\n addDelegatedListener(\n type: string,\n selector: string,\n handler: (e: Event) => void\n ): void;\n }\n\nexport function attachCopyDelegation(host: DelegatedHost) {\n host.addDelegatedListener('click', '.nc-copy-btn', async (e: Event) => {\n e.stopPropagation();\n const btn = (e.target as HTMLElement)?.closest('.nc-copy-btn') as HTMLElement;\n if (!btn) return;\n const row = btn?.closest('.nc-copy') as HTMLElement | null;\n const value = row?.dataset.copy ?? '';\n if (!value) return;\n\n try {\n await copyToClipboard(value);\n btn.classList.add('copied');\n const prev = btn.getAttribute('aria-label') || 'Copy';\n btn.setAttribute('aria-label', 'Copied!');\n setTimeout(() => {\n btn.classList.remove('copied');\n btn.setAttribute('aria-label', prev);\n }, 1200);\n } catch {\n // optional: surface error // todo\n }\n });\n}\n"],"names":["renderTextRow","opts","value","display","className","monospace","title","showCopyButton","safeClassName","safeDisplay","escapeHtml","safeTitle","iconHtml","safeValue","renderNpub","npub","maskNPub","renderNip05","nip05","renderName","options","name","attachCopyDelegation","host","e","btn","_a","row","copyToClipboard","prev"],"mappings":"kDAaO,SAASA,EAAcC,EAA8B,CACpD,KAAA,CACJ,MAAAC,EACA,QAAAC,EAAUD,EACV,UAAAE,EAAY,GACZ,UAAAC,EAAY,GACZ,MAAAC,EAAQH,EACR,eAAAI,EAAiB,EAAA,EACfN,EAGEO,EAAgBJ,EAAU,QAAQ,YAAa,EAAE,EACjDK,EAAcC,EAAWP,CAAO,EAChCQ,EAAcD,EAAWJ,CAAK,EAC9BM,EAAc,WAEpB,GAAIL,EAAgB,CACZ,MAAAM,EAAYH,EAAWR,CAAK,EAE3B,MAAA;AAAA,oBADU,oBAAoBG,EAAY,OAAS,EAAE,IAAIG,CAAa,GAAG,KAAK,CAE7D,gBAAgBK,CAAS,YAAYF,CAAS;AAAA,qCACrCF,CAAW;AAAA;AAAA;AAAA;AAAA;AAAA,iBAK/BG,CAAQ;AAAA;AAAA,KAAA,CAMhB,MAAA;AAAA,kBADU,YAAYP,EAAY,OAAS,EAAE,IAAIG,CAAa,GAAG,KAAK,CAErD,YAAYG,CAAS;AAAA,QACvCF,CAAW;AAAA;AAAA,GAGnB,CC7CO,SAASK,EACdC,EACQ,CAER,OAAOf,EAAc,CACnB,MAAOe,EACP,QAASC,EAASD,CAAI,EACtB,UAAW,GACX,eAAgB,GAChB,MAAOA,CAAA,CACR,CACH,CCZO,SAASE,EACdC,EACQ,CACR,OAAOlB,EAAc,CACnB,MAAOkB,EACP,QAASA,EACT,UAAW,GACX,eAAgB,EAAA,CACjB,CACH,CCFO,SAASC,EAAWC,EAAoC,CAC7D,KAAM,CAAE,KAAAC,EAAM,UAAAjB,EAAY,GAAI,MAAAE,EAAO,eAAAC,EAAiB,IAAUa,EAEhE,OAAOpB,EAAc,CACnB,MAAOqB,EACP,QAASA,EACT,UAAW,sBAAsBjB,CAAS,GAAG,KAAK,EAClD,MAAOE,GAASe,EAChB,eAAAd,CAAA,CACD,CACH,CCTO,SAASe,EAAqBC,EAAqB,CACxDA,EAAK,qBAAqB,QAAS,eAAgB,MAAOC,GAAa,OACrEA,EAAE,gBAAgB,EAClB,MAAMC,GAAOC,EAAAF,EAAE,SAAF,YAAAE,EAA0B,QAAQ,gBAC/C,GAAI,CAACD,EAAK,OACJ,MAAAE,EAAMF,GAAA,YAAAA,EAAK,QAAQ,YACnBvB,GAAQyB,GAAA,YAAAA,EAAK,QAAQ,OAAQ,GACnC,GAAKzB,EAED,GAAA,CACF,MAAM0B,EAAgB1B,CAAK,EACvBuB,EAAA,UAAU,IAAI,QAAQ,EAC1B,MAAMI,EAAOJ,EAAI,aAAa,YAAY,GAAK,OAC3CA,EAAA,aAAa,aAAc,SAAS,EACxC,WAAW,IAAM,CACXA,EAAA,UAAU,OAAO,QAAQ,EACzBA,EAAA,aAAa,aAAcI,CAAI,GAClC,IAAI,CAAA,MACD,CAAA,CAER,CACD,CACH"}
@@ -1,5 +1,5 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/zap-utils-B1sz0Abx.js","assets/nostr-service-pr_crY62.js","assets/utils--bxLbhGF.js"])))=>i.map(i=>d[i]);
2
- import{_ as y}from"./preload-helper-D7HrI6pR.js";import"./dialog-component-Da1ZIYh9.js";import{getBatchedProfileMetadata as x,extractProfileMetadataContent as k}from"./zap-utils-B1sz0Abx.js";import{h as p,e as g,f as b,c as v}from"./base-styles-CBypR3FR.js";import"./nostr-service-pr_crY62.js";import"./utils--bxLbhGF.js";function $(t="light"){const e=t==="dark";return`
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/zap-utils-KFUD_vTU.js","assets/nostr-service-CnaPxjc6.js","assets/utils--bxLbhGF.js"])))=>i.map(i=>d[i]);
2
+ import{_ as y}from"./preload-helper-D7HrI6pR.js";import"./dialog-component-Da1ZIYh9.js";import{getBatchedProfileMetadata as x,extractProfileMetadataContent as k}from"./zap-utils-KFUD_vTU.js";import{j as p,e as g,k as b,i as v}from"./base-styles-BSEzBDsk.js";import"./nostr-service-CnaPxjc6.js";import"./utils--bxLbhGF.js";function $(t="light"){const e=t==="dark";return`
3
3
  .likers-dialog-content {
4
4
  padding: 0;
5
5
  max-height: 60vh;
@@ -234,5 +234,5 @@ import{_ as y}from"./preload-helper-D7HrI6pR.js";import"./dialog-component-Da1ZI
234
234
  ${t.map((a,n)=>P(a,e[n],n)).join("")}
235
235
  </div>
236
236
  </div>
237
- `}async function N(t,e){const r=t.querySelector(".likers-list");if(!r)return;const a=[...new Set(e.map(n=>n.authorPubkey))];console.log("Nostr-Components: Likers dialog: Fetching profiles for",a.length,"unique authors");try{const n=await x(a),i=new Map;n.forEach(o=>{i.set(o.id,o.profile)});const s=new Map;a.forEach(o=>{s.set(o,p(o))});for(let o=0;o<e.length;o++){const l=e[o],c=i.get(l.authorPubkey),d=s.get(l.authorPubkey)||l.authorPubkey;let u;if(c){const h=k(c);u={...l,authorName:h.display_name||h.name||d,authorPicture:h.picture,authorNpub:d}}else u={...l,authorName:d,authorNpub:d};const f=r.querySelector(`[data-like-index="${o}"]`);if(f){const h=m(u,o);f.outerHTML=h}}console.log("Nostr-Components: Likers dialog: Progressive enhancement completed for",e.length,"like entries")}catch(n){console.error("Nostr-Components: Likers dialog: Error in batched profile enhancement",n),console.log("Nostr-Components: Likers dialog: Falling back to individual profile fetching"),await E(t,e)}}async function E(t,e){const r=t.querySelector(".likers-list");if(!r)return;const a=new Map,n=e.map(async(i,s)=>{if(a.has(i.authorPubkey)){const o=a.get(i.authorPubkey);return{index:s,enhanced:{...i,authorName:o.authorName,authorPicture:o.authorPicture,authorNpub:o.authorNpub}}}try{const{getProfileMetadata:o}=await y(async()=>{const{getProfileMetadata:f}=await import("./zap-utils-B1sz0Abx.js");return{getProfileMetadata:f}},__vite__mapDeps([0,1,2])),l=await o(i.authorPubkey),c=k(l),d=p(i.authorPubkey),u={...i,authorName:c.display_name||c.name||d,authorPicture:c.picture,authorNpub:d};return a.set(i.authorPubkey,u),{index:s,enhanced:u}}catch(o){console.error("Nostr-Components: Likers dialog: Error fetching profile for",i.authorPubkey,o);const l=p(i.authorPubkey),c={...i,authorName:l,authorNpub:l};return a.set(i.authorPubkey,c),{index:s,enhanced:c}}});for(const i of n)try{const{index:s,enhanced:o}=await i,l=r.querySelector(`[data-like-index="${s}"]`);if(l){const c=m(o,s);l.outerHTML=c}}catch(s){console.error("Nostr-Components: Likers dialog: Error processing profile enhancement",s)}}export{w as injectLikersDialogStyles,z as openLikersDialog};
238
- //# sourceMappingURL=dialog-likers-BjiCHFan.js.map
237
+ `}async function N(t,e){const r=t.querySelector(".likers-list");if(!r)return;const a=[...new Set(e.map(n=>n.authorPubkey))];console.log("Nostr-Components: Likers dialog: Fetching profiles for",a.length,"unique authors");try{const n=await x(a),i=new Map;n.forEach(o=>{i.set(o.id,o.profile)});const s=new Map;a.forEach(o=>{s.set(o,p(o))});for(let o=0;o<e.length;o++){const l=e[o],c=i.get(l.authorPubkey),d=s.get(l.authorPubkey)||l.authorPubkey;let u;if(c){const h=k(c);u={...l,authorName:h.display_name||h.name||d,authorPicture:h.picture,authorNpub:d}}else u={...l,authorName:d,authorNpub:d};const f=r.querySelector(`[data-like-index="${o}"]`);if(f){const h=m(u,o);f.outerHTML=h}}console.log("Nostr-Components: Likers dialog: Progressive enhancement completed for",e.length,"like entries")}catch(n){console.error("Nostr-Components: Likers dialog: Error in batched profile enhancement",n),console.log("Nostr-Components: Likers dialog: Falling back to individual profile fetching"),await E(t,e)}}async function E(t,e){const r=t.querySelector(".likers-list");if(!r)return;const a=new Map,n=e.map(async(i,s)=>{if(a.has(i.authorPubkey)){const o=a.get(i.authorPubkey);return{index:s,enhanced:{...i,authorName:o.authorName,authorPicture:o.authorPicture,authorNpub:o.authorNpub}}}try{const{getProfileMetadata:o}=await y(async()=>{const{getProfileMetadata:f}=await import("./zap-utils-KFUD_vTU.js");return{getProfileMetadata:f}},__vite__mapDeps([0,1,2])),l=await o(i.authorPubkey),c=k(l),d=p(i.authorPubkey),u={...i,authorName:c.display_name||c.name||d,authorPicture:c.picture,authorNpub:d};return a.set(i.authorPubkey,u),{index:s,enhanced:u}}catch(o){console.error("Nostr-Components: Likers dialog: Error fetching profile for",i.authorPubkey,o);const l=p(i.authorPubkey),c={...i,authorName:l,authorNpub:l};return a.set(i.authorPubkey,c),{index:s,enhanced:c}}});for(const i of n)try{const{index:s,enhanced:o}=await i,l=r.querySelector(`[data-like-index="${s}"]`);if(l){const c=m(o,s);l.outerHTML=c}}catch(s){console.error("Nostr-Components: Likers dialog: Error processing profile enhancement",s)}}export{w as injectLikersDialogStyles,z as openLikersDialog};
238
+ //# sourceMappingURL=dialog-likers-BqDn2P_3.js.map
@@ -1 +1 @@
1
- {"version":3,"mappings":"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","names":["getLikersDialogStyles","theme","isDark","injectLikersDialogStyles","style","renderLikeEntry","like","index","authorNameSafe","escapeHtml","njumpUrl","hexToNpub","profilePictureSafe","isValidUrl","profilePicture","isDislike","statusText","statusClass","formatRelativeTime","renderSkeletonLikeEntry","npub","openLikersDialog","params","likeDetails","dialogComponent","initialContent","renderInitialContent","dialogElement","_a","dialog","enhanceLikeDetailsProgressively","npubs","likersList","uniqueAuthorIds","profileResults","getBatchedProfileMetadata","profileMap","result","npubMap","pubkey","profile","enhanced","profileContent","extractProfileMetadataContent","skeletonEntry","enhancedEntry","error","enhanceLikeDetailsIndividually","profileCache","profilePromises","cachedProfile","getProfileMetadata","__vitePreload","profileMetadata","promise"],"ignoreList":[],"sources":["../../src/nostr-like/dialog-likers-style.ts","../../src/nostr-like/dialog-likers.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nexport function getLikersDialogStyles(theme: 'light' | 'dark' = 'light'): string {\n const isDark = theme === 'dark';\n \n return `\n .likers-dialog-content {\n padding: 0;\n max-height: 60vh;\n overflow-y: auto;\n }\n\n .likers-list {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .like-entry {\n display: flex;\n align-items: center;\n padding: 12px;\n border-radius: 8px;\n background: ${isDark ? '#2a2a2a' : '#f8f9fa'};\n border: 1px solid ${isDark ? '#3a3a3a' : '#e9ecef'};\n transition: background-color 0.2s ease;\n }\n\n .like-entry:hover {\n background: ${isDark ? '#3a3a3a' : '#e9ecef'};\n }\n\n .like-author-info {\n display: flex;\n align-items: center;\n gap: 12px;\n width: 100%;\n }\n\n .like-author-picture {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n object-fit: cover;\n flex-shrink: 0;\n }\n\n .like-author-picture-default {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: ${isDark ? '#3a3a3a' : '#e9ecef'};\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n }\n\n .like-author-details {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n min-width: 0;\n }\n\n .like-author-link {\n color: ${isDark ? '#ffffff' : '#000000'};\n text-decoration: none;\n font-weight: 500;\n font-size: 14px;\n transition: color 0.2s ease;\n }\n\n .like-author-link:hover {\n color: ${isDark ? '#4a9eff' : '#1877f2'};\n text-decoration: underline;\n }\n\n .like-date {\n color: ${isDark ? '#b0b0b0' : '#65676b'};\n font-size: 12px;\n font-weight: 400;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .like-status {\n font-weight: 500;\n font-size: 11px;\n padding: 2px 6px;\n border-radius: 4px;\n }\n\n .like-status.liked {\n color: ${isDark ? '#4a9eff' : '#1877f2'};\n background: ${isDark ? 'rgba(74, 158, 255, 0.1)' : 'rgba(24, 119, 242, 0.1)'};\n }\n\n .like-status.disliked {\n color: #d32f2f;\n background: rgba(211, 47, 47, 0.1);\n }\n\n .no-likes {\n text-align: center;\n color: ${isDark ? '#b0b0b0' : '#65676b'};\n font-size: 14px;\n padding: 40px 20px;\n }\n\n /* Skeleton loading states */\n .skeleton-entry {\n opacity: 0.7;\n }\n\n .skeleton-picture {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: linear-gradient(90deg, \n ${isDark ? '#3a3a3a' : '#f0f0f0'} 25%, \n ${isDark ? '#4a4a4a' : '#e0e0e0'} 50%, \n ${isDark ? '#3a3a3a' : '#f0f0f0'} 75%\n );\n background-size: 200% 100%;\n animation: skeleton-loading 1.5s infinite;\n flex-shrink: 0;\n }\n\n .skeleton-name {\n width: 120px;\n height: 14px;\n background: linear-gradient(90deg, \n ${isDark ? '#3a3a3a' : '#f0f0f0'} 25%, \n ${isDark ? '#4a4a4a' : '#e0e0e0'} 50%, \n ${isDark ? '#3a3a3a' : '#f0f0f0'} 75%\n );\n background-size: 200% 100%;\n animation: skeleton-loading 1.5s infinite;\n border-radius: 2px;\n }\n\n @keyframes skeleton-loading {\n 0% {\n background-position: -200% 0;\n }\n 100% {\n background-position: 200% 0;\n }\n }\n\n /* Responsive */\n @media (max-width: 480px) {\n .likers-dialog-content {\n max-height: 70vh;\n }\n\n .like-entry {\n padding: 10px;\n }\n\n .like-author-picture,\n .like-author-picture-default,\n .skeleton-picture {\n width: 36px;\n height: 36px;\n }\n\n .like-author-link {\n font-size: 13px;\n }\n\n .like-date {\n font-size: 11px;\n }\n }\n\n /* Scrollbar styling */\n .likers-dialog-content::-webkit-scrollbar {\n width: 6px;\n }\n\n .likers-dialog-content::-webkit-scrollbar-track {\n background: ${isDark ? '#2a2a2a' : '#f1f1f1'};\n border-radius: 3px;\n }\n\n .likers-dialog-content::-webkit-scrollbar-thumb {\n background: ${isDark ? '#555' : '#c1c1c1'};\n border-radius: 3px;\n }\n\n .likers-dialog-content::-webkit-scrollbar-thumb:hover {\n background: ${isDark ? '#777' : '#a8a8a8'};\n }\n `;\n}\n","// SPDX-License-Identifier: MIT\n\n// Import for side effects to register the custom element\nimport '../base/dialog-component/dialog-component';\nimport type { DialogComponent } from '../base/dialog-component/dialog-component';\nimport { getLikersDialogStyles } from './dialog-likers-style';\nimport { getBatchedProfileMetadata, extractProfileMetadataContent } from '../nostr-zap/zap-utils';\nimport { escapeHtml, formatRelativeTime, hexToNpub, isValidUrl } from '../common/utils';\nimport { LikeDetails } from './like-utils';\n\n/**\n * Modal dialog for displaying individual like details (likers).\n * \n * Shows a list of all users who liked a URL with:\n * - User's name\n * - User's profile picture\n * - Time of like (relative time)\n * - Clickable links to user profiles via njump.me\n */\n\nexport interface OpenLikersModalParams {\n likeDetails: LikeDetails[];\n theme?: 'light' | 'dark';\n}\n\n/**\n * Inject likers dialog content styles into document head\n * Prevents duplicate injection by checking for existing styles\n */\nexport const injectLikersDialogStyles = (theme: 'light' | 'dark' = 'light') => {\n // Remove existing likers dialog styles\n const existingStyles = document.querySelectorAll('style[data-likers-dialog-styles]');\n existingStyles.forEach(style => style.remove());\n \n const style = document.createElement('style');\n style.setAttribute('data-likers-dialog-styles', 'true');\n style.textContent = getLikersDialogStyles(theme);\n document.head.appendChild(style);\n}\n\ninterface EnhancedLikeDetails extends LikeDetails {\n authorName?: string;\n authorPicture?: string;\n authorNpub?: string;\n}\n\n/**\n * Render individual like entry HTML (with profile data)\n */\nfunction renderLikeEntry(like: EnhancedLikeDetails, index: number): string {\n const authorNameSafe = escapeHtml(like.authorName || 'Unknown liker');\n const npubSafe = like.authorNpub || hexToNpub(like.authorPubkey);\n const njumpUrl = `https://njump.me/${npubSafe}`;\n const profilePictureSafe = isValidUrl(like.authorPicture || '') ? like.authorPicture || '' : '';\n \n const profilePicture = profilePictureSafe \n ? `<img src=\"${profilePictureSafe}\" alt=\"${authorNameSafe}\" class=\"like-author-picture\" />`\n : `<div class=\"like-author-picture-default\">👤</div>`;\n \n const isDislike = like.content === '-';\n const statusText = isDislike ? 'Disliked' : 'Liked';\n const statusClass = isDislike ? 'disliked' : 'liked';\n \n return `\n <div class=\"like-entry\" data-like-index=\"${index}\" data-author-pubkey=\"${like.authorPubkey}\">\n <div class=\"like-author-info\">\n ${profilePicture}\n <div class=\"like-author-details\">\n <a href=\"${njumpUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"like-author-link\">\n ${authorNameSafe}\n </a>\n <div class=\"like-date\">\n ${formatRelativeTime(Math.floor(like.date.getTime() / 1000))}\n <span class=\"like-status ${statusClass}\">${statusText}</span>\n </div>\n </div>\n </div>\n </div>\n `;\n}\n\n/**\n * Render skeleton like entry HTML (with npub)\n */\nfunction renderSkeletonLikeEntry(like: LikeDetails, npub: string, index: number): string {\n const isDislike = like.content === '-';\n const statusText = isDislike ? 'Disliked' : 'Liked';\n const statusClass = isDislike ? 'disliked' : 'liked';\n \n return `\n <div class=\"like-entry skeleton-entry\" data-like-index=\"${index}\" data-author-pubkey=\"${like.authorPubkey}\">\n <div class=\"like-author-info\">\n <div class=\"skeleton-picture\"></div>\n <div class=\"like-author-details\">\n <div class=\"like-author-link skeleton-name\">\n ${escapeHtml(npub)}\n </div>\n <div class=\"like-date\">\n ${formatRelativeTime(Math.floor(like.date.getTime() / 1000))}\n <span class=\"like-status ${statusClass}\">${statusText}</span>\n </div>\n </div>\n </div>\n </div>\n `;\n}\n\n/**\n * Opens the likers dialog showing individual like details\n */\nexport async function openLikersDialog(params: OpenLikersModalParams): Promise<DialogComponent> {\n const { likeDetails, theme = 'light' } = params;\n \n // Inject styles\n injectLikersDialogStyles(theme);\n \n // Ensure custom element is defined\n if (!customElements.get('dialog-component')) {\n await customElements.whenDefined('dialog-component');\n }\n \n // Create dialog component (not added to DOM)\n const dialogComponent = document.createElement('dialog-component') as DialogComponent;\n dialogComponent.setAttribute('header', 'Likers');\n if (params.theme) {\n dialogComponent.setAttribute('data-theme', params.theme);\n }\n \n // Initial content with skeleton loaders showing npubs\n const initialContent = await renderInitialContent(likeDetails);\n dialogComponent.innerHTML = initialContent;\n \n // Show the dialog (this will create and append the actual dialog element)\n dialogComponent.showModal();\n \n // Get the actual dialog element for progressive enhancement\n const dialogElement: HTMLDialogElement | null = \n dialogComponent.querySelector('.nostr-base-dialog') ||\n dialogComponent.shadowRoot?.querySelector('.nostr-base-dialog') ||\n document.body.querySelector('.nostr-base-dialog');\n \n if (!dialogElement) {\n console.error('[openLikersDialog] Failed to find dialog element after showModal()');\n throw new Error('Dialog element not found. The dialog may not have been created properly.');\n }\n \n // Type assertion: dialog is guaranteed to be non-null after the check above\n const dialog = dialogElement as HTMLDialogElement;\n \n // Start progressive enhancement\n if (dialog && likeDetails.length > 0) {\n enhanceLikeDetailsProgressively(dialog, likeDetails);\n }\n\n return dialogComponent;\n}\n\n/**\n * Render initial dialog content with skeleton loaders showing npubs\n */\nasync function renderInitialContent(likeDetails: LikeDetails[]): Promise<string> {\n if (likeDetails.length === 0) {\n return `\n <div class=\"likers-dialog-content\">\n <div class=\"likers-list\">\n <div class=\"no-likes\">No likes yet</div>\n </div>\n </div>\n `;\n }\n\n // Convert all pubkeys to npubs for immediate display\n const npubs = likeDetails.map(like => hexToNpub(like.authorPubkey));\n\n const skeletonEntries = likeDetails.map((like, index) => \n renderSkeletonLikeEntry(like, npubs[index], index)\n ).join('');\n\n return `\n <div class=\"likers-dialog-content\">\n <div class=\"likers-list\">\n ${skeletonEntries}\n </div>\n </div>\n `;\n}\n\n/**\n * Progressively enhance like details with profile information (batched approach)\n */\nasync function enhanceLikeDetailsProgressively(dialog: HTMLDialogElement, likeDetails: LikeDetails[]): Promise<void> {\n const likersList = dialog.querySelector('.likers-list') as HTMLElement;\n if (!likersList) return;\n\n // Get unique author IDs\n const uniqueAuthorIds = [...new Set(likeDetails.map(like => like.authorPubkey))];\n console.log(\"Nostr-Components: Likers dialog: Fetching profiles for\", uniqueAuthorIds.length, \"unique authors\");\n\n try {\n // Fetch all profiles in a single batched call\n const profileResults = await getBatchedProfileMetadata(uniqueAuthorIds);\n \n // Create a map for quick lookup\n const profileMap = new Map<string, any>();\n profileResults.forEach(result => {\n profileMap.set(result.id, result.profile);\n });\n\n // Convert all pubkeys to npubs for display\n const npubMap = new Map<string, string>();\n uniqueAuthorIds.forEach(pubkey => {\n npubMap.set(pubkey, hexToNpub(pubkey));\n });\n\n // Process each like entry\n for (let index = 0; index < likeDetails.length; index++) {\n const like = likeDetails[index];\n const profile = profileMap.get(like.authorPubkey);\n const npub = npubMap.get(like.authorPubkey) || like.authorPubkey;\n \n let enhanced: EnhancedLikeDetails;\n \n if (profile) {\n const profileContent = extractProfileMetadataContent(profile);\n enhanced = {\n ...like,\n authorName: profileContent.display_name || profileContent.name || npub,\n authorPicture: profileContent.picture,\n authorNpub: npub,\n };\n } else {\n // Fallback if profile not found\n enhanced = {\n ...like,\n authorName: npub,\n authorNpub: npub,\n };\n }\n\n // Find the corresponding skeleton entry by index and replace it\n const skeletonEntry = likersList.querySelector(`[data-like-index=\"${index}\"]`);\n if (skeletonEntry) {\n const enhancedEntry = renderLikeEntry(enhanced, index);\n skeletonEntry.outerHTML = enhancedEntry;\n }\n }\n\n console.log(\"Nostr-Components: Likers dialog: Progressive enhancement completed for\", likeDetails.length, \"like entries\");\n } catch (error) {\n console.error(\"Nostr-Components: Likers dialog: Error in batched profile enhancement\", error);\n \n // Fallback to individual processing if batched approach fails\n console.log(\"Nostr-Components: Likers dialog: Falling back to individual profile fetching\");\n await enhanceLikeDetailsIndividually(dialog, likeDetails);\n }\n}\n\n/**\n * Fallback: Enhance like details individually (original approach)\n */\nasync function enhanceLikeDetailsIndividually(dialog: HTMLDialogElement, likeDetails: LikeDetails[]): Promise<void> {\n const likersList = dialog.querySelector('.likers-list') as HTMLElement;\n if (!likersList) return;\n\n // Create a map to track which profiles we've already fetched\n const profileCache = new Map<string, EnhancedLikeDetails>();\n \n // Fetch all profile metadata in parallel\n const profilePromises = likeDetails.map(async (like, index) => {\n // Check if we already have this profile cached\n if (profileCache.has(like.authorPubkey)) {\n const cachedProfile = profileCache.get(like.authorPubkey)!;\n return {\n index,\n enhanced: {\n ...like,\n authorName: cachedProfile.authorName,\n authorPicture: cachedProfile.authorPicture,\n authorNpub: cachedProfile.authorNpub,\n }\n };\n }\n\n try {\n const { getProfileMetadata } = await import('../nostr-zap/zap-utils');\n const profileMetadata = await getProfileMetadata(like.authorPubkey);\n const profileContent = extractProfileMetadataContent(profileMetadata);\n const npub = hexToNpub(like.authorPubkey);\n \n const enhanced = {\n ...like,\n authorName: profileContent.display_name || profileContent.name || npub,\n authorPicture: profileContent.picture,\n authorNpub: npub,\n };\n\n // Cache the profile for other entries from the same author\n profileCache.set(like.authorPubkey, enhanced);\n \n return {\n index,\n enhanced\n };\n } catch (error) {\n console.error(\"Nostr-Components: Likers dialog: Error fetching profile for\", like.authorPubkey, error);\n // Fallback with just pubkey converted to npub\n const npub = hexToNpub(like.authorPubkey);\n const enhanced = {\n ...like,\n authorName: npub,\n authorNpub: npub,\n };\n \n // Cache the fallback profile\n profileCache.set(like.authorPubkey, enhanced);\n \n return {\n index,\n enhanced\n };\n }\n });\n\n // Process each profile as it becomes available\n for (const promise of profilePromises) {\n try {\n const { index, enhanced } = await promise;\n \n // Find the corresponding skeleton entry by index and replace it\n const skeletonEntry = likersList.querySelector(`[data-like-index=\"${index}\"]`);\n if (skeletonEntry) {\n const enhancedEntry = renderLikeEntry(enhanced, index);\n skeletonEntry.outerHTML = enhancedEntry;\n }\n } catch (error) {\n console.error(\"Nostr-Components: Likers dialog: Error processing profile enhancement\", error);\n }\n }\n}\n"],"file":"assets/dialog-likers-BjiCHFan.js"}
1
+ {"version":3,"mappings":"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","names":["getLikersDialogStyles","theme","isDark","injectLikersDialogStyles","style","renderLikeEntry","like","index","authorNameSafe","escapeHtml","njumpUrl","hexToNpub","profilePictureSafe","isValidUrl","profilePicture","isDislike","statusText","statusClass","formatRelativeTime","renderSkeletonLikeEntry","npub","openLikersDialog","params","likeDetails","dialogComponent","initialContent","renderInitialContent","dialogElement","_a","dialog","enhanceLikeDetailsProgressively","npubs","likersList","uniqueAuthorIds","profileResults","getBatchedProfileMetadata","profileMap","result","npubMap","pubkey","profile","enhanced","profileContent","extractProfileMetadataContent","skeletonEntry","enhancedEntry","error","enhanceLikeDetailsIndividually","profileCache","profilePromises","cachedProfile","getProfileMetadata","__vitePreload","profileMetadata","promise"],"ignoreList":[],"sources":["../../src/nostr-like/dialog-likers-style.ts","../../src/nostr-like/dialog-likers.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nexport function getLikersDialogStyles(theme: 'light' | 'dark' = 'light'): string {\n const isDark = theme === 'dark';\n \n return `\n .likers-dialog-content {\n padding: 0;\n max-height: 60vh;\n overflow-y: auto;\n }\n\n .likers-list {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .like-entry {\n display: flex;\n align-items: center;\n padding: 12px;\n border-radius: 8px;\n background: ${isDark ? '#2a2a2a' : '#f8f9fa'};\n border: 1px solid ${isDark ? '#3a3a3a' : '#e9ecef'};\n transition: background-color 0.2s ease;\n }\n\n .like-entry:hover {\n background: ${isDark ? '#3a3a3a' : '#e9ecef'};\n }\n\n .like-author-info {\n display: flex;\n align-items: center;\n gap: 12px;\n width: 100%;\n }\n\n .like-author-picture {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n object-fit: cover;\n flex-shrink: 0;\n }\n\n .like-author-picture-default {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: ${isDark ? '#3a3a3a' : '#e9ecef'};\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n }\n\n .like-author-details {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n min-width: 0;\n }\n\n .like-author-link {\n color: ${isDark ? '#ffffff' : '#000000'};\n text-decoration: none;\n font-weight: 500;\n font-size: 14px;\n transition: color 0.2s ease;\n }\n\n .like-author-link:hover {\n color: ${isDark ? '#4a9eff' : '#1877f2'};\n text-decoration: underline;\n }\n\n .like-date {\n color: ${isDark ? '#b0b0b0' : '#65676b'};\n font-size: 12px;\n font-weight: 400;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .like-status {\n font-weight: 500;\n font-size: 11px;\n padding: 2px 6px;\n border-radius: 4px;\n }\n\n .like-status.liked {\n color: ${isDark ? '#4a9eff' : '#1877f2'};\n background: ${isDark ? 'rgba(74, 158, 255, 0.1)' : 'rgba(24, 119, 242, 0.1)'};\n }\n\n .like-status.disliked {\n color: #d32f2f;\n background: rgba(211, 47, 47, 0.1);\n }\n\n .no-likes {\n text-align: center;\n color: ${isDark ? '#b0b0b0' : '#65676b'};\n font-size: 14px;\n padding: 40px 20px;\n }\n\n /* Skeleton loading states */\n .skeleton-entry {\n opacity: 0.7;\n }\n\n .skeleton-picture {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: linear-gradient(90deg, \n ${isDark ? '#3a3a3a' : '#f0f0f0'} 25%, \n ${isDark ? '#4a4a4a' : '#e0e0e0'} 50%, \n ${isDark ? '#3a3a3a' : '#f0f0f0'} 75%\n );\n background-size: 200% 100%;\n animation: skeleton-loading 1.5s infinite;\n flex-shrink: 0;\n }\n\n .skeleton-name {\n width: 120px;\n height: 14px;\n background: linear-gradient(90deg, \n ${isDark ? '#3a3a3a' : '#f0f0f0'} 25%, \n ${isDark ? '#4a4a4a' : '#e0e0e0'} 50%, \n ${isDark ? '#3a3a3a' : '#f0f0f0'} 75%\n );\n background-size: 200% 100%;\n animation: skeleton-loading 1.5s infinite;\n border-radius: 2px;\n }\n\n @keyframes skeleton-loading {\n 0% {\n background-position: -200% 0;\n }\n 100% {\n background-position: 200% 0;\n }\n }\n\n /* Responsive */\n @media (max-width: 480px) {\n .likers-dialog-content {\n max-height: 70vh;\n }\n\n .like-entry {\n padding: 10px;\n }\n\n .like-author-picture,\n .like-author-picture-default,\n .skeleton-picture {\n width: 36px;\n height: 36px;\n }\n\n .like-author-link {\n font-size: 13px;\n }\n\n .like-date {\n font-size: 11px;\n }\n }\n\n /* Scrollbar styling */\n .likers-dialog-content::-webkit-scrollbar {\n width: 6px;\n }\n\n .likers-dialog-content::-webkit-scrollbar-track {\n background: ${isDark ? '#2a2a2a' : '#f1f1f1'};\n border-radius: 3px;\n }\n\n .likers-dialog-content::-webkit-scrollbar-thumb {\n background: ${isDark ? '#555' : '#c1c1c1'};\n border-radius: 3px;\n }\n\n .likers-dialog-content::-webkit-scrollbar-thumb:hover {\n background: ${isDark ? '#777' : '#a8a8a8'};\n }\n `;\n}\n","// SPDX-License-Identifier: MIT\n\n// Import for side effects to register the custom element\nimport '../base/dialog-component/dialog-component';\nimport type { DialogComponent } from '../base/dialog-component/dialog-component';\nimport { getLikersDialogStyles } from './dialog-likers-style';\nimport { getBatchedProfileMetadata, extractProfileMetadataContent } from '../nostr-zap/zap-utils';\nimport { escapeHtml, formatRelativeTime, hexToNpub, isValidUrl } from '../common/utils';\nimport { LikeDetails } from './like-utils';\n\n/**\n * Modal dialog for displaying individual like details (likers).\n * \n * Shows a list of all users who liked a URL with:\n * - User's name\n * - User's profile picture\n * - Time of like (relative time)\n * - Clickable links to user profiles via njump.me\n */\n\nexport interface OpenLikersModalParams {\n likeDetails: LikeDetails[];\n theme?: 'light' | 'dark';\n}\n\n/**\n * Inject likers dialog content styles into document head\n * Prevents duplicate injection by checking for existing styles\n */\nexport const injectLikersDialogStyles = (theme: 'light' | 'dark' = 'light') => {\n // Remove existing likers dialog styles\n const existingStyles = document.querySelectorAll('style[data-likers-dialog-styles]');\n existingStyles.forEach(style => style.remove());\n \n const style = document.createElement('style');\n style.setAttribute('data-likers-dialog-styles', 'true');\n style.textContent = getLikersDialogStyles(theme);\n document.head.appendChild(style);\n}\n\ninterface EnhancedLikeDetails extends LikeDetails {\n authorName?: string;\n authorPicture?: string;\n authorNpub?: string;\n}\n\n/**\n * Render individual like entry HTML (with profile data)\n */\nfunction renderLikeEntry(like: EnhancedLikeDetails, index: number): string {\n const authorNameSafe = escapeHtml(like.authorName || 'Unknown liker');\n const npubSafe = like.authorNpub || hexToNpub(like.authorPubkey);\n const njumpUrl = `https://njump.me/${npubSafe}`;\n const profilePictureSafe = isValidUrl(like.authorPicture || '') ? like.authorPicture || '' : '';\n \n const profilePicture = profilePictureSafe \n ? `<img src=\"${profilePictureSafe}\" alt=\"${authorNameSafe}\" class=\"like-author-picture\" />`\n : `<div class=\"like-author-picture-default\">👤</div>`;\n \n const isDislike = like.content === '-';\n const statusText = isDislike ? 'Disliked' : 'Liked';\n const statusClass = isDislike ? 'disliked' : 'liked';\n \n return `\n <div class=\"like-entry\" data-like-index=\"${index}\" data-author-pubkey=\"${like.authorPubkey}\">\n <div class=\"like-author-info\">\n ${profilePicture}\n <div class=\"like-author-details\">\n <a href=\"${njumpUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"like-author-link\">\n ${authorNameSafe}\n </a>\n <div class=\"like-date\">\n ${formatRelativeTime(Math.floor(like.date.getTime() / 1000))}\n <span class=\"like-status ${statusClass}\">${statusText}</span>\n </div>\n </div>\n </div>\n </div>\n `;\n}\n\n/**\n * Render skeleton like entry HTML (with npub)\n */\nfunction renderSkeletonLikeEntry(like: LikeDetails, npub: string, index: number): string {\n const isDislike = like.content === '-';\n const statusText = isDislike ? 'Disliked' : 'Liked';\n const statusClass = isDislike ? 'disliked' : 'liked';\n \n return `\n <div class=\"like-entry skeleton-entry\" data-like-index=\"${index}\" data-author-pubkey=\"${like.authorPubkey}\">\n <div class=\"like-author-info\">\n <div class=\"skeleton-picture\"></div>\n <div class=\"like-author-details\">\n <div class=\"like-author-link skeleton-name\">\n ${escapeHtml(npub)}\n </div>\n <div class=\"like-date\">\n ${formatRelativeTime(Math.floor(like.date.getTime() / 1000))}\n <span class=\"like-status ${statusClass}\">${statusText}</span>\n </div>\n </div>\n </div>\n </div>\n `;\n}\n\n/**\n * Opens the likers dialog showing individual like details\n */\nexport async function openLikersDialog(params: OpenLikersModalParams): Promise<DialogComponent> {\n const { likeDetails, theme = 'light' } = params;\n \n // Inject styles\n injectLikersDialogStyles(theme);\n \n // Ensure custom element is defined\n if (!customElements.get('dialog-component')) {\n await customElements.whenDefined('dialog-component');\n }\n \n // Create dialog component (not added to DOM)\n const dialogComponent = document.createElement('dialog-component') as DialogComponent;\n dialogComponent.setAttribute('header', 'Likers');\n if (params.theme) {\n dialogComponent.setAttribute('data-theme', params.theme);\n }\n \n // Initial content with skeleton loaders showing npubs\n const initialContent = await renderInitialContent(likeDetails);\n dialogComponent.innerHTML = initialContent;\n \n // Show the dialog (this will create and append the actual dialog element)\n dialogComponent.showModal();\n \n // Get the actual dialog element for progressive enhancement\n const dialogElement: HTMLDialogElement | null = \n dialogComponent.querySelector('.nostr-base-dialog') ||\n dialogComponent.shadowRoot?.querySelector('.nostr-base-dialog') ||\n document.body.querySelector('.nostr-base-dialog');\n \n if (!dialogElement) {\n console.error('[openLikersDialog] Failed to find dialog element after showModal()');\n throw new Error('Dialog element not found. The dialog may not have been created properly.');\n }\n \n // Type assertion: dialog is guaranteed to be non-null after the check above\n const dialog = dialogElement as HTMLDialogElement;\n \n // Start progressive enhancement\n if (dialog && likeDetails.length > 0) {\n enhanceLikeDetailsProgressively(dialog, likeDetails);\n }\n\n return dialogComponent;\n}\n\n/**\n * Render initial dialog content with skeleton loaders showing npubs\n */\nasync function renderInitialContent(likeDetails: LikeDetails[]): Promise<string> {\n if (likeDetails.length === 0) {\n return `\n <div class=\"likers-dialog-content\">\n <div class=\"likers-list\">\n <div class=\"no-likes\">No likes yet</div>\n </div>\n </div>\n `;\n }\n\n // Convert all pubkeys to npubs for immediate display\n const npubs = likeDetails.map(like => hexToNpub(like.authorPubkey));\n\n const skeletonEntries = likeDetails.map((like, index) => \n renderSkeletonLikeEntry(like, npubs[index], index)\n ).join('');\n\n return `\n <div class=\"likers-dialog-content\">\n <div class=\"likers-list\">\n ${skeletonEntries}\n </div>\n </div>\n `;\n}\n\n/**\n * Progressively enhance like details with profile information (batched approach)\n */\nasync function enhanceLikeDetailsProgressively(dialog: HTMLDialogElement, likeDetails: LikeDetails[]): Promise<void> {\n const likersList = dialog.querySelector('.likers-list') as HTMLElement;\n if (!likersList) return;\n\n // Get unique author IDs\n const uniqueAuthorIds = [...new Set(likeDetails.map(like => like.authorPubkey))];\n console.log(\"Nostr-Components: Likers dialog: Fetching profiles for\", uniqueAuthorIds.length, \"unique authors\");\n\n try {\n // Fetch all profiles in a single batched call\n const profileResults = await getBatchedProfileMetadata(uniqueAuthorIds);\n \n // Create a map for quick lookup\n const profileMap = new Map<string, any>();\n profileResults.forEach(result => {\n profileMap.set(result.id, result.profile);\n });\n\n // Convert all pubkeys to npubs for display\n const npubMap = new Map<string, string>();\n uniqueAuthorIds.forEach(pubkey => {\n npubMap.set(pubkey, hexToNpub(pubkey));\n });\n\n // Process each like entry\n for (let index = 0; index < likeDetails.length; index++) {\n const like = likeDetails[index];\n const profile = profileMap.get(like.authorPubkey);\n const npub = npubMap.get(like.authorPubkey) || like.authorPubkey;\n \n let enhanced: EnhancedLikeDetails;\n \n if (profile) {\n const profileContent = extractProfileMetadataContent(profile);\n enhanced = {\n ...like,\n authorName: profileContent.display_name || profileContent.name || npub,\n authorPicture: profileContent.picture,\n authorNpub: npub,\n };\n } else {\n // Fallback if profile not found\n enhanced = {\n ...like,\n authorName: npub,\n authorNpub: npub,\n };\n }\n\n // Find the corresponding skeleton entry by index and replace it\n const skeletonEntry = likersList.querySelector(`[data-like-index=\"${index}\"]`);\n if (skeletonEntry) {\n const enhancedEntry = renderLikeEntry(enhanced, index);\n skeletonEntry.outerHTML = enhancedEntry;\n }\n }\n\n console.log(\"Nostr-Components: Likers dialog: Progressive enhancement completed for\", likeDetails.length, \"like entries\");\n } catch (error) {\n console.error(\"Nostr-Components: Likers dialog: Error in batched profile enhancement\", error);\n \n // Fallback to individual processing if batched approach fails\n console.log(\"Nostr-Components: Likers dialog: Falling back to individual profile fetching\");\n await enhanceLikeDetailsIndividually(dialog, likeDetails);\n }\n}\n\n/**\n * Fallback: Enhance like details individually (original approach)\n */\nasync function enhanceLikeDetailsIndividually(dialog: HTMLDialogElement, likeDetails: LikeDetails[]): Promise<void> {\n const likersList = dialog.querySelector('.likers-list') as HTMLElement;\n if (!likersList) return;\n\n // Create a map to track which profiles we've already fetched\n const profileCache = new Map<string, EnhancedLikeDetails>();\n \n // Fetch all profile metadata in parallel\n const profilePromises = likeDetails.map(async (like, index) => {\n // Check if we already have this profile cached\n if (profileCache.has(like.authorPubkey)) {\n const cachedProfile = profileCache.get(like.authorPubkey)!;\n return {\n index,\n enhanced: {\n ...like,\n authorName: cachedProfile.authorName,\n authorPicture: cachedProfile.authorPicture,\n authorNpub: cachedProfile.authorNpub,\n }\n };\n }\n\n try {\n const { getProfileMetadata } = await import('../nostr-zap/zap-utils');\n const profileMetadata = await getProfileMetadata(like.authorPubkey);\n const profileContent = extractProfileMetadataContent(profileMetadata);\n const npub = hexToNpub(like.authorPubkey);\n \n const enhanced = {\n ...like,\n authorName: profileContent.display_name || profileContent.name || npub,\n authorPicture: profileContent.picture,\n authorNpub: npub,\n };\n\n // Cache the profile for other entries from the same author\n profileCache.set(like.authorPubkey, enhanced);\n \n return {\n index,\n enhanced\n };\n } catch (error) {\n console.error(\"Nostr-Components: Likers dialog: Error fetching profile for\", like.authorPubkey, error);\n // Fallback with just pubkey converted to npub\n const npub = hexToNpub(like.authorPubkey);\n const enhanced = {\n ...like,\n authorName: npub,\n authorNpub: npub,\n };\n \n // Cache the fallback profile\n profileCache.set(like.authorPubkey, enhanced);\n \n return {\n index,\n enhanced\n };\n }\n });\n\n // Process each profile as it becomes available\n for (const promise of profilePromises) {\n try {\n const { index, enhanced } = await promise;\n \n // Find the corresponding skeleton entry by index and replace it\n const skeletonEntry = likersList.querySelector(`[data-like-index=\"${index}\"]`);\n if (skeletonEntry) {\n const enhancedEntry = renderLikeEntry(enhanced, index);\n skeletonEntry.outerHTML = enhancedEntry;\n }\n } catch (error) {\n console.error(\"Nostr-Components: Likers dialog: Error processing profile enhancement\", error);\n }\n }\n}\n"],"file":"assets/dialog-likers-BqDn2P_3.js"}