nostr-components 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{dialog-component-Dqg0QU9I.js → dialog-component-Da1ZIYh9.js} +7 -2
- package/dist/assets/dialog-component-Da1ZIYh9.js.map +1 -0
- package/dist/assets/{dialog-likers-BzTesCZa.js → dialog-likers-BjiCHFan.js} +2 -2
- package/dist/assets/{dialog-likers-BzTesCZa.js.map → dialog-likers-BjiCHFan.js.map} +1 -1
- package/dist/assets/nostr-user-component-BOdux8_6.js +2 -0
- package/dist/assets/nostr-user-component-BOdux8_6.js.map +1 -0
- package/dist/components/nostr-follow-button.es.js +1 -1
- package/dist/components/nostr-like.es.js +3 -3
- package/dist/components/nostr-like.es.js.map +1 -1
- package/dist/components/nostr-profile-badge.es.js +1 -1
- package/dist/components/nostr-profile.es.js +1 -1
- package/dist/components/nostr-zap.es.js +2 -2
- package/dist/components/nostr-zap.es.js.map +1 -1
- package/dist/nostr-components.es.js +1 -1
- package/dist/nostr-components.umd.js +9 -4
- package/dist/nostr-components.umd.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/dialog-component-Dqg0QU9I.js.map +0 -1
- package/dist/assets/nostr-user-component-Q7GeeFyu.js +0 -2
- package/dist/assets/nostr-user-component-Q7GeeFyu.js.map +0 -1
|
@@ -8,8 +8,13 @@ var d=Object.defineProperty;var c=(a,t,e)=>t in a?d(a,t,{enumerable:!0,configura
|
|
|
8
8
|
padding: var(--nostrc-spacing-xl, 20px);
|
|
9
9
|
background: var(--nostrc-theme-bg, #ffffff);
|
|
10
10
|
color: var(--nostrc-theme-text-primary, #000000);
|
|
11
|
-
|
|
11
|
+
margin: auto;
|
|
12
12
|
font-family: var(--nostrc-font-family-primary, ui-sans-serif, system-ui, sans-serif);
|
|
13
|
+
/* Ensure dialog is centered when opened */
|
|
14
|
+
position: fixed;
|
|
15
|
+
top: 50%;
|
|
16
|
+
left: 50%;
|
|
17
|
+
transform: translate(-50%, -50%);
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
.nostr-base-dialog[open] {
|
|
@@ -63,4 +68,4 @@ var d=Object.defineProperty;var c=(a,t,e)=>t in a?d(a,t,{enumerable:!0,configura
|
|
|
63
68
|
color: var(--nostrc-theme-text-primary, #000000);
|
|
64
69
|
}
|
|
65
70
|
`;class g extends HTMLElement{constructor(){super();l(this,"dialog",null)}static get observedAttributes(){return["header","data-theme"]}injectStyles(){if(document.querySelector("style[data-dialog-component-styles]"))return;const e=document.createElement("style");e.setAttribute("data-dialog-component-styles","true"),e.textContent=h(),document.head.appendChild(e)}render(){this.injectStyles();const e=this.getAttribute("header")||"Dialog",o=this.getAttribute("data-theme");this.dialog=document.createElement("dialog"),this.dialog.className="nostr-base-dialog",o&&this.dialog.setAttribute("data-theme",o);const i=document.createElement("div");i.className="dialog-header";const s=document.createElement("h2");s.textContent=e;const n=document.createElement("button");n.className="dialog-close-btn",n.setAttribute("aria-label","Close dialog"),n.textContent="✕",i.appendChild(s),i.appendChild(n);const r=document.createElement("div");for(r.className="dialog-content";this.firstChild;)r.appendChild(this.firstChild);this.dialog.appendChild(i),this.dialog.appendChild(r),document.body.appendChild(this.dialog),this.setupEventListeners()}setupEventListeners(){if(!this.dialog)return;const e=this.dialog.querySelector(".dialog-close-btn");e==null||e.addEventListener("click",()=>{this.close()}),this.dialog.addEventListener("click",o=>{o.target===this.dialog&&this.close()}),this.dialog.addEventListener("cancel",o=>{o.preventDefault(),this.close()}),this.dialog.addEventListener("close",()=>{this.cleanup()})}show(){this.showModal()}showModal(){var e;this.dialog||this.render(),(e=this.dialog)==null||e.showModal()}close(){var e;(e=this.dialog)==null||e.close()}cleanup(){this.dialog&&this.dialog.isConnected&&this.dialog.remove(),this.isConnected&&this.remove(),this.dialog=null}disconnectedCallback(){this.cleanup()}attributeChangedCallback(e,o,i){if(e==="header"&&this.dialog){const s=this.dialog.querySelector(".dialog-header h2");s&&(s.textContent=i||"Dialog")}else e==="data-theme"&&this.dialog&&(i?this.dialog.setAttribute("data-theme",i):this.dialog.removeAttribute("data-theme"))}}customElements.get("dialog-component")||customElements.define("dialog-component",g);
|
|
66
|
-
//# sourceMappingURL=dialog-component-
|
|
71
|
+
//# sourceMappingURL=dialog-component-Da1ZIYh9.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog-component-Da1ZIYh9.js","sources":["../../src/base/dialog-component/style.ts","../../src/base/dialog-component/dialog-component.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\n/**\n * Base dialog component styles\n * Provides common styling for all dialog components\n */\nexport const getDialogComponentStyles = (): string => {\n return `\n /* Base Dialog Styles */\n .nostr-base-dialog {\n width: 400px;\n max-width: 90vw;\n border: none;\n border-radius: var(--nostrc-border-radius-lg, 10px);\n padding: var(--nostrc-spacing-xl, 20px);\n background: var(--nostrc-theme-bg, #ffffff);\n color: var(--nostrc-theme-text-primary, #000000);\n margin: auto;\n font-family: var(--nostrc-font-family-primary, ui-sans-serif, system-ui, sans-serif);\n /* Ensure dialog is centered when opened */\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n }\n\n .nostr-base-dialog[open] {\n display: block;\n }\n\n .nostr-base-dialog::backdrop {\n background: rgba(0, 0, 0, 0.5);\n }\n\n .dialog-header {\n position: relative;\n margin-bottom: var(--nostrc-spacing-lg, 16px);\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .dialog-header h2 {\n font-size: var(--nostrc-font-size-large, 1.25rem);\n font-weight: var(--nostrc-font-weight-bold, 700);\n margin: 0;\n flex: 1;\n text-align: left;\n padding-top: 2px;\n color: var(--nostrc-theme-text-primary, #000000);\n }\n\n .dialog-close-btn {\n border: none;\n background: var(--nostrc-theme-hover-bg, #f7fafc);\n border-radius: var(--nostrc-border-radius-full, 50%);\n width: 32px;\n height: 32px;\n min-width: 32px;\n font-size: var(--nostrc-font-size-base, 16px);\n cursor: pointer;\n color: var(--nostrc-theme-text-secondary, #666666);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .dialog-close-btn:hover {\n background: var(--nostrc-theme-border, rgba(0, 0, 0, 0.05));\n color: var(--nostrc-theme-text-primary, #000000);\n }\n\n .dialog-content {\n line-height: 1.6;\n color: var(--nostrc-theme-text-primary, #000000);\n }\n `;\n};\n\n","// SPDX-License-Identifier: MIT\n\nimport { getDialogComponentStyles } from './style';\n\n/**\n * Base dialog component that extends HTMLElement\n * Provides common dialog functionality with header, close button, and content area\n * \n * Usage:\n * ```typescript\n * const dialog = document.createElement('dialog-component');\n * dialog.setAttribute('header', 'Dialog Title');\n * dialog.innerHTML = '<p>Your content goes here</p>';\n * dialog.showModal(); // Don't append to body, just call showModal()\n * ```\n * \n * Features:\n * - Header with customizable text\n * - Close button\n * - Click outside to close\n * - ESC key to close\n * - Automatic cleanup on close\n * \n * Important: Only one instance of this component should be added to the DOM at any time.\n * Multiple instances may cause conflicts with event listeners and cleanup behavior.\n */\nexport class DialogComponent extends HTMLElement {\n private dialog: HTMLDialogElement | null = null;\n\n constructor() {\n super();\n }\n\n /**\n * Observed attributes for the component\n */\n static get observedAttributes() {\n return ['header', 'data-theme'];\n }\n\n /**\n * Inject dialog styles into document head\n * Prevents duplicate injection by checking for existing styles\n */\n private injectStyles(): void {\n if (document.querySelector('style[data-dialog-component-styles]')) return;\n \n const style = document.createElement('style');\n style.setAttribute('data-dialog-component-styles', 'true');\n style.textContent = getDialogComponentStyles();\n document.head.appendChild(style);\n }\n\n /**\n * Render the dialog\n */\n private render(): void {\n this.injectStyles();\n\n const headerText = this.getAttribute('header') || 'Dialog';\n const theme = this.getAttribute('data-theme');\n\n this.dialog = document.createElement('dialog');\n this.dialog.className = 'nostr-base-dialog';\n if (theme) {\n this.dialog.setAttribute('data-theme', theme);\n }\n\n const headerDiv = document.createElement('div');\n headerDiv.className = 'dialog-header';\n \n const headerH2 = document.createElement('h2');\n headerH2.textContent = headerText;\n \n const closeBtn = document.createElement('button');\n closeBtn.className = 'dialog-close-btn';\n closeBtn.setAttribute('aria-label', 'Close dialog');\n closeBtn.textContent = '✕';\n \n headerDiv.appendChild(headerH2);\n headerDiv.appendChild(closeBtn);\n\n const contentDiv = document.createElement('div');\n contentDiv.className = 'dialog-content';\n \n // Safely move child nodes from component to dialog content\n // This preserves any existing DOM nodes without using innerHTML\n while (this.firstChild) {\n contentDiv.appendChild(this.firstChild);\n }\n\n this.dialog.appendChild(headerDiv);\n this.dialog.appendChild(contentDiv);\n\n document.body.appendChild(this.dialog);\n\n this.setupEventListeners();\n }\n\n /**\n * Setup event listeners for closing the dialog\n */\n private setupEventListeners(): void {\n if (!this.dialog) return;\n\n // Close button click\n const closeBtn = this.dialog.querySelector('.dialog-close-btn');\n closeBtn?.addEventListener('click', () => {\n this.close();\n });\n\n // Click outside dialog (on backdrop)\n this.dialog.addEventListener('click', (e) => {\n if (e.target === this.dialog) {\n this.close();\n }\n });\n\n // ESC key handler\n this.dialog.addEventListener('cancel', (e) => {\n e.preventDefault();\n this.close();\n });\n\n // Cleanup on close\n this.dialog.addEventListener('close', () => {\n this.cleanup();\n });\n }\n\n /**\n * Show the dialog (alias for showModal)\n */\n public show(): void {\n this.showModal();\n }\n\n /**\n * Show the dialog as modal\n */\n public showModal(): void {\n if (!this.dialog) {\n this.render();\n }\n this.dialog?.showModal();\n }\n\n /**\n * Close the dialog\n */\n public close(): void {\n this.dialog?.close();\n }\n\n /**\n * Cleanup when dialog is closed\n */\n private cleanup(): void {\n if (this.dialog && this.dialog.isConnected) {\n this.dialog.remove();\n }\n if (this.isConnected) {\n this.remove();\n }\n this.dialog = null;\n }\n\n /**\n * Called when component is removed from DOM\n */\n disconnectedCallback(): void {\n this.cleanup();\n }\n\n /**\n * Called when observed attributes change\n */\n attributeChangedCallback(name: string, _oldValue: string, newValue: string): void {\n if (name === 'header' && this.dialog) {\n const heading = this.dialog.querySelector('.dialog-header h2');\n if (heading) {\n heading.textContent = newValue || 'Dialog';\n }\n } else if (name === 'data-theme' && this.dialog) {\n if (newValue) {\n this.dialog.setAttribute('data-theme', newValue);\n } else {\n this.dialog.removeAttribute('data-theme');\n }\n }\n }\n}\n\n// Define custom element\nif (!customElements.get('dialog-component')) {\n customElements.define('dialog-component', DialogComponent);\n}\n\n"],"names":["getDialogComponentStyles","DialogComponent","__publicField","style","headerText","theme","headerDiv","headerH2","closeBtn","contentDiv","e","_a","name","_oldValue","newValue","heading"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
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-
|
|
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`
|
|
3
3
|
.likers-dialog-content {
|
|
4
4
|
padding: 0;
|
|
5
5
|
max-height: 60vh;
|
|
@@ -235,4 +235,4 @@ import{_ as y}from"./preload-helper-D7HrI6pR.js";import"./dialog-component-Dqg0Q
|
|
|
235
235
|
</div>
|
|
236
236
|
</div>
|
|
237
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-
|
|
238
|
+
//# sourceMappingURL=dialog-likers-BjiCHFan.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-BzTesCZa.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-BjiCHFan.js"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var l=Object.defineProperty;var p=(i,r,e)=>r in i?l(i,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[r]=e;var n=(i,r,e)=>p(i,typeof r!="symbol"?r+"":r,e);import{N as h,a as o}from"./base-styles-CBypR3FR.js";import{U as c}from"./user-resolver-C-E6KdwY.js";const d="nc:user";class C extends h{constructor(e=!0){super(e);n(this,"user",null);n(this,"profile",null);n(this,"userStatus",this.channel("user"));n(this,"loadSeq",0);n(this,"resolver",new c(this.nostrService))}static get observedAttributes(){return[...super.observedAttributes,"npub","pubkey","nip05"]}connectedCallback(){var e;(e=super.connectedCallback)==null||e.call(this),this.userStatus.get()==o.Idle&&this.initChannelStatus("user",o.Loading,{reflectOverall:!1}),this.validateInputs()&&this.resolveUserAndProfile().catch(t=>{console.error("[NostrUserComponent] init failed:",t)})}attributeChangedCallback(e,t,s){var u;t!==s&&((u=super.attributeChangedCallback)==null||u.call(this,e,t,s),(e==="npub"||e==="nip05"||e==="pubkey")&&this.validateInputs()&&this.resolveUserAndProfile())}validateInputs(){if(!super.validateInputs())return this.userStatus.set(o.Idle),!1;const e=this.getAttribute("npub"),t=this.getAttribute("pubkey"),s=this.getAttribute("nip05"),u=this.tagName.toLowerCase(),a=this.resolver.validateInputs({npub:e,pubkey:t,nip05:s});return a?(this.userStatus.set(o.Error,a),console.error(`Nostr-Components: ${u}: ${a}`),!1):(this.errorMessage="",!0)}async resolveUserAndProfile(){const e=++this.loadSeq;try{await this.ensureNostrConnected()}catch(t){if(e!==this.loadSeq)return;console.error("[NostrUserComponent] Relay connect failed before user/profile load:",t);return}this.userStatus.set(o.Loading);try{const{user:t,profile:s}=await this.resolver.resolveUser({npub:this.getAttribute("npub"),pubkey:this.getAttribute("pubkey"),nip05:this.getAttribute("nip05")});if(e!==this.loadSeq)return;if(s==null){this.userStatus.set(o.Error,"Profile not found");return}this.user=t,this.profile=s,this.userStatus.set(o.Ready),this.dispatchEvent(new CustomEvent(d,{detail:{user:this.user,profile:this.profile},bubbles:!0,composed:!0})),this.onUserReady(this.user,this.profile)}catch(t){if(e!==this.loadSeq)return;const s=t instanceof Error?t.message:"Failed to load user/profile";console.error("[NostrUserComponent] "+s,t),this.userStatus.set(o.Error,s)}}renderContent(){}onUserReady(e,t){}}export{C as N};
|
|
2
|
+
//# sourceMappingURL=nostr-user-component-BOdux8_6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nostr-user-component-BOdux8_6.js","sources":["../../src/base/user-component/nostr-user-component.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport { NDKUser, NDKUserProfile } from '@nostr-dev-kit/ndk';\nimport { NostrBaseComponent, NCStatus } from '../base-component/nostr-base-component';\nimport { UserResolver } from '../resolvers/user-resolver';\n\nconst EVT_USER = 'nc:user';\n\n/**\n * NostrUserComponent\n * ==================\n * Extension of `NostrBaseComponent` that resolves and manages a Nostr user.\n *\n * Overview\n * - Accepts identity attributes (`npub`, `nip05`, or `pubkey`) and validates them.\n * - Resolves an `NDKUser` via the shared `nostrService` and fetches its profile.\n * - Exposes resolved `user` and `profile` to subclasses for rendering or logic.\n * - Emits lifecycle events for status and user readiness.\n *\n * Observed attributes\n * - `npub` — user's Nostr public key (bech32 npub)\n * - `nip05` — NIP-05 identifier (e.g. `alice@example.com`)\n * - `pubkey` — raw hex-encoded public key\n *\n * Events\n * - `nc:status` — from base, reflects connection and user/profile loading status\n * - `nc:user` — fired when a user and profile are successfully resolved\n */\n\nexport class NostrUserComponent extends NostrBaseComponent {\n\n protected user: NDKUser | null = null;\n protected profile: NDKUserProfile | null = null;\n\n protected userStatus = this.channel('user');\n\n // guard to ignore stale user fetches\n private loadSeq = 0;\n\n private resolver = new UserResolver(this.nostrService);\n\n constructor(shadow: boolean = true) {\n super(shadow);\n }\n\n /** Lifecycle methods */\n static get observedAttributes() {\n return [\n ...super.observedAttributes,\n 'npub',\n 'pubkey',\n 'nip05',\n ];\n }\n\n connectedCallback() {\n super.connectedCallback?.();\n\n if (this.userStatus.get() == NCStatus.Idle) {\n this.initChannelStatus('user', NCStatus.Loading, { reflectOverall: false });\n }\n\n if (this.validateInputs()) {\n this.resolveUserAndProfile().catch(e => {\n console.error('[NostrUserComponent] init failed:', e);\n });\n }\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null\n ) {\n if (oldValue === newValue) return;\n super.attributeChangedCallback?.(name, oldValue, newValue);\n\n if (name === 'npub' || name === 'nip05' || name === 'pubkey') {\n if (this.validateInputs()) {\n // Re-resolve user + profile on identity changes\n void this.resolveUserAndProfile();\n }\n }\n }\n\n /** Protected methods */\n protected validateInputs(): boolean {\n\n if (!super.validateInputs()) {\n this.userStatus.set(NCStatus.Idle);\n return false;\n }\n\n const npub = this.getAttribute(\"npub\");\n const pubkey = this.getAttribute(\"pubkey\");\n const nip05 = this.getAttribute(\"nip05\");\n const tagName = this.tagName.toLowerCase();\n\n const err = this.resolver.validateInputs({\n npub: npub,\n pubkey: pubkey,\n nip05: nip05,\n });\n\n if (err) {\n this.userStatus.set(NCStatus.Error, err);\n console.error(`Nostr-Components: ${tagName}: ${err}`);\n return false;\n }\n\n this.errorMessage = \"\";\n return true;\n\n }\n\n protected async resolveUserAndProfile(): Promise<void> {\n const seq = ++this.loadSeq; // token to prevent stale writes\n\n // Ensure relays are connected; handle failure inside to avoid unhandled rejection\n try {\n await this.ensureNostrConnected();\n } catch (e) {\n if (seq !== this.loadSeq) return; // stale\n // Base already set status=Error, but make the failure explicit here too\n console.error('[NostrUserComponent] Relay connect failed before user/profile load:', e);\n return;\n }\n\n this.userStatus.set(NCStatus.Loading);\n\n try {\n const { user, profile } = await this.resolver.resolveUser({\n npub: this.getAttribute('npub'),\n pubkey: this.getAttribute('pubkey'),\n nip05: this.getAttribute('nip05'),\n });\n\n // stale call check\n if (seq !== this.loadSeq) return;\n\n if (profile == null) {\n this.userStatus.set(NCStatus.Error, \"Profile not found\");\n return;\n }\n\n this.user = user;\n this.profile = profile;\n this.userStatus.set(NCStatus.Ready);\n // Notify listeners that user + profile are available\n this.dispatchEvent(new CustomEvent(EVT_USER, {\n detail: { user: this.user, profile: this.profile },\n bubbles: true,\n composed: true,\n }));\n this.onUserReady(this.user!, this.profile);\n } catch (err) {\n if (seq !== this.loadSeq) return; // stale\n const msg = err instanceof Error ? err.message : 'Failed to load user/profile';\n console.error('[NostrUserComponent] ' + msg, err);\n this.userStatus.set(NCStatus.Error, msg);\n }\n }\n\n protected renderContent() { }\n\n /** Hook for subclasses to react when user/profile are ready (e.g., render). */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected onUserReady(_user: NDKUser, _profile: NDKUserProfile | null) { }\n}\n"],"names":["EVT_USER","NostrUserComponent","NostrBaseComponent","shadow","__publicField","UserResolver","_a","NCStatus","e","name","oldValue","newValue","npub","pubkey","nip05","tagName","err","seq","user","profile","msg","_user","_profile"],"mappings":"yQAMA,MAAMA,EAAW,UAuBV,MAAMC,UAA2BC,CAAmB,CAYzD,YAAYC,EAAkB,GAAM,CAClC,MAAMA,CAAM,EAXJC,EAAA,YAAuB,MACvBA,EAAA,eAAiC,MAEjCA,EAAA,kBAAa,KAAK,QAAQ,MAAM,GAGlCA,EAAA,eAAU,GAEVA,EAAA,gBAAW,IAAIC,EAAa,KAAK,YAAY,EAGvC,CAId,WAAW,oBAAqB,CACvB,MAAA,CACL,GAAG,MAAM,mBACT,OACA,SACA,OACF,CAAA,CAGF,mBAAoB,QAClBC,EAAA,MAAM,oBAAN,MAAAA,EAAA,WAEI,KAAK,WAAW,IAAI,GAAKC,EAAS,MACpC,KAAK,kBAAkB,OAAQA,EAAS,QAAS,CAAE,eAAgB,GAAO,EAGxE,KAAK,kBACF,KAAA,sBAAA,EAAwB,MAAWC,GAAA,CAC9B,QAAA,MAAM,oCAAqCA,CAAC,CAAA,CACrD,CACH,CAGF,yBACEC,EACAC,EACAC,EACA,OACID,IAAaC,KACXL,EAAA,MAAA,2BAAA,MAAAA,EAAA,UAA2BG,EAAMC,EAAUC,IAE7CF,IAAS,QAAUA,IAAS,SAAWA,IAAS,WAC9C,KAAK,kBAEF,KAAK,sBAAsB,EAEpC,CAIQ,gBAA0B,CAE9B,GAAA,CAAC,MAAM,iBACJ,YAAA,WAAW,IAAIF,EAAS,IAAI,EAC1B,GAGH,MAAAK,EAAU,KAAK,aAAa,MAAM,EAClCC,EAAU,KAAK,aAAa,QAAQ,EACpCC,EAAU,KAAK,aAAa,OAAO,EACnCC,EAAU,KAAK,QAAQ,YAAY,EAEnCC,EAAM,KAAK,SAAS,eAAe,CACvC,KAAAJ,EACA,OAAAC,EACA,MAAAC,CAAA,CACD,EAED,OAAIE,GACF,KAAK,WAAW,IAAIT,EAAS,MAAOS,CAAG,EACvC,QAAQ,MAAM,qBAAqBD,CAAO,KAAKC,CAAG,EAAE,EAC7C,KAGT,KAAK,aAAe,GACb,GAAA,CAIT,MAAgB,uBAAuC,CAC/C,MAAAC,EAAM,EAAE,KAAK,QAGf,GAAA,CACF,MAAM,KAAK,qBAAqB,QACzBT,EAAG,CACN,GAAAS,IAAQ,KAAK,QAAS,OAElB,QAAA,MAAM,sEAAuET,CAAC,EACtF,MAAA,CAGG,KAAA,WAAW,IAAID,EAAS,OAAO,EAEhC,GAAA,CACF,KAAM,CAAE,KAAAW,EAAM,QAAAC,CAAA,EAAY,MAAM,KAAK,SAAS,YAAY,CACxD,KAAM,KAAK,aAAa,MAAM,EAC9B,OAAQ,KAAK,aAAa,QAAQ,EAClC,MAAO,KAAK,aAAa,OAAO,CAAA,CACjC,EAGG,GAAAF,IAAQ,KAAK,QAAS,OAE1B,GAAIE,GAAW,KAAM,CACnB,KAAK,WAAW,IAAIZ,EAAS,MAAO,mBAAmB,EACvD,MAAA,CAGF,KAAK,KAAOW,EACZ,KAAK,QAAUC,EACV,KAAA,WAAW,IAAIZ,EAAS,KAAK,EAE7B,KAAA,cAAc,IAAI,YAAYP,EAAU,CAC3C,OAAQ,CAAE,KAAM,KAAK,KAAM,QAAS,KAAK,OAAQ,EACjD,QAAS,GACT,SAAU,EAAA,CACX,CAAC,EACF,KAAK,YAAY,KAAK,KAAO,KAAK,OAAO,QAClCgB,EAAK,CACR,GAAAC,IAAQ,KAAK,QAAS,OAC1B,MAAMG,EAAMJ,aAAe,MAAQA,EAAI,QAAU,8BACzC,QAAA,MAAM,wBAA0BI,EAAKJ,CAAG,EAChD,KAAK,WAAW,IAAIT,EAAS,MAAOa,CAAG,CAAA,CACzC,CAGQ,eAAgB,CAAA,CAIhB,YAAYC,EAAgBC,EAAiC,CAAA,CACzE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var v=Object.defineProperty;var w=(n,e,o)=>e in n?v(n,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):n[e]=o;var f=(n,e,o)=>w(n,typeof e!="symbol"?e+"":e,o);import{d as m}from"../assets/nostr-service-pr_crY62.js";import{N as p}from"../assets/nostr-user-component-
|
|
1
|
+
var v=Object.defineProperty;var w=(n,e,o)=>e in n?v(n,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):n[e]=o;var f=(n,e,o)=>w(n,typeof e!="symbol"?e+"":e,o);import{d as m}from"../assets/nostr-service-pr_crY62.js";import{N as p}from"../assets/nostr-user-component-BOdux8_6.js";import{b as y,g as k,a as b}from"../assets/theme-C1r1Zw8r.js";import{e as c,g as S,a as l}from"../assets/base-styles-CBypR3FR.js";import"../assets/user-resolver-C-E6KdwY.js";import"../assets/icons-Dr_d9MII.js";function C({isLoading:n,isError:e,errorMessage:o,isFollowed:t,isFollowing:r,showAvatar:i=!1,user:s,profile:a,customText:h="Follow me on nostr"}){if(r)return F();if(n)return x();if(e)return N(o||"");const u=t?y("light"):i&&s&&(a!=null&&a.image)?`<img src="${c(a.image)}" alt="${c(a.name||s.npub)}" class="user-avatar" />`:k(),g=t?"Followed":`<span>${c(h)}</span>`;return d(u,g)}function x(){return d(b(),"<span>Loading...</span>")}function F(){return d(b(),"<span>Following...</span>")}function N(n){return d('<div class="error-icon">⚠</div>',c(n))}function d(n,e){return`
|
|
2
2
|
<div class='nostr-follow-button-container'>
|
|
3
3
|
<div class='nostr-follow-button-left-container'>
|
|
4
4
|
${n}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/dialog-likers-
|
|
2
|
-
var m=Object.defineProperty;var w=(r,e,t)=>e in r?m(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var d=(r,e,t)=>w(r,typeof e!="symbol"?e+"":e,t);import{_ as y}from"../assets/preload-helper-D7HrI6pR.js";import{e as b,g as L,N as x,a as i,c as C}from"../assets/base-styles-CBypR3FR.js";import{S as f,e as g}from"../assets/nostr-service-pr_crY62.js";import"../assets/dialog-component-
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/dialog-likers-BjiCHFan.js","assets/preload-helper-D7HrI6pR.js","assets/dialog-component-Da1ZIYh9.js","assets/zap-utils-B1sz0Abx.js","assets/nostr-service-pr_crY62.js","assets/utils--bxLbhGF.js","assets/base-styles-CBypR3FR.js"])))=>i.map(i=>d[i]);
|
|
2
|
+
var m=Object.defineProperty;var w=(r,e,t)=>e in r?m(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var d=(r,e,t)=>w(r,typeof e!="symbol"?e+"":e,t);import{_ as y}from"../assets/preload-helper-D7HrI6pR.js";import{e as b,g as L,N as x,a as i,c as C}from"../assets/base-styles-CBypR3FR.js";import{S as f,e as g}from"../assets/nostr-service-pr_crY62.js";import"../assets/dialog-component-Da1ZIYh9.js";function S({isLoading:r,isError:e,errorMessage:t,buttonText:n,isLiked:o,likeCount:s,hasLikes:a=!1,isCountLoading:l=!1,theme:u="light"}){if(e)return E(t||"");const c=N(o,u),h=o?"<span>Liked</span>":`<span>${b(n)}</span>`;return U(c,h,s,a,o,r,l)}function E(r){return A('<div class="error-icon">⚠</div>',b(r))}function A(r,e){return`
|
|
3
3
|
<div class="nostr-like-button-container">
|
|
4
4
|
<div class="nostr-like-button-left-container">
|
|
5
5
|
${r}
|
|
@@ -289,7 +289,7 @@ var m=Object.defineProperty;var w=(r,e,t)=>e in r?m(r,e,{enumerable:!0,configura
|
|
|
289
289
|
<li>Works with a browser extension like <a href="https://getalby.com" target="_blank" rel="noopener noreferrer">Alby</a> or nos2x</li>
|
|
290
290
|
</ul>
|
|
291
291
|
</div>
|
|
292
|
-
`,e.showModal()};new TextDecoder("utf-8");new TextEncoder;function k(r){r.indexOf("://")===-1&&(r="wss://"+r);let e=new URL(r);return e.pathname=e.pathname.replace(/\/+/g,"/"),e.pathname.endsWith("/")&&(e.pathname=e.pathname.slice(0,-1)),(e.port==="80"&&e.protocol==="ws:"||e.port==="443"&&e.protocol==="wss:")&&(e.port=""),e.searchParams.sort(),e.hash="",e.toString()}async function I(r,e){const t=k(r),n=new f;try{const o=await n.querySync(e,{kinds:[17],"#k":["web"],"#i":[t],limit:1e3}),s=[];let a=0,l=0;for(const c of o)s.push({authorPubkey:c.pubkey,date:new Date(c.created_at*1e3),content:c.content}),c.content==="-"?l++:a++;return s.sort((c,h)=>h.date.getTime()-c.date.getTime()),{totalCount:a-l,likeDetails:s,likedCount:a,dislikedCount:l}}catch(o){throw o instanceof Error?o:new Error(String(o))}finally{n.close(e)}}function v(r,e){return{kind:17,content:e,tags:[["k","web"],["i",r]],created_at:Math.floor(Date.now()/1e3)}}function M(r){return v(r,"+")}function H(r){return v(r,"-")}async function P(r,e,t){const n=new f,o=r;try{const s=await n.querySync(t,{kinds:[17],authors:[e],"#k":["web"],"#i":[o],limit:1});if(s.length===0)return!1;const a=s[0];return a.content==="+"||a.content===""}catch(s){return console.error("Nostr-Components: Like button: Error checking user like status",s),!1}finally{n.close(t)}}async function T(){try{if(typeof window<"u"&&window.nostr)return await window.nostr.getPublicKey()}catch(r){console.error("Nostr-Components: Like button: Error getting user pubkey",r)}return null}async function p(r){try{if(typeof window<"u"&&window.nostr)return await window.nostr.signEvent(r);throw new Error("NIP-07 extension not available")}catch(e){throw console.error("Nostr-Components: Like button: Error signing event",e),e}}function _(){return typeof window<"u"&&!!window.nostr}class F extends x{constructor(){super();d(this,"likeActionStatus",this.channel("likeAction"));d(this,"likeListStatus",this.channel("likeList"));d(this,"currentUrl","");d(this,"isLiked",!1);d(this,"likeCount",0);d(this,"cachedLikeDetails",null);d(this,"loadSeq",0)}connectedCallback(){var t;(t=super.connectedCallback)==null||t.call(this),this.likeListStatus.get()===i.Idle&&this.
|
|
292
|
+
`,e.showModal()};new TextDecoder("utf-8");new TextEncoder;function k(r){r.indexOf("://")===-1&&(r="wss://"+r);let e=new URL(r);return e.pathname=e.pathname.replace(/\/+/g,"/"),e.pathname.endsWith("/")&&(e.pathname=e.pathname.slice(0,-1)),(e.port==="80"&&e.protocol==="ws:"||e.port==="443"&&e.protocol==="wss:")&&(e.port=""),e.searchParams.sort(),e.hash="",e.toString()}async function I(r,e){const t=k(r),n=new f;try{const o=await n.querySync(e,{kinds:[17],"#k":["web"],"#i":[t],limit:1e3}),s=[];let a=0,l=0;for(const c of o)s.push({authorPubkey:c.pubkey,date:new Date(c.created_at*1e3),content:c.content}),c.content==="-"?l++:a++;return s.sort((c,h)=>h.date.getTime()-c.date.getTime()),{totalCount:a-l,likeDetails:s,likedCount:a,dislikedCount:l}}catch(o){throw o instanceof Error?o:new Error(String(o))}finally{n.close(e)}}function v(r,e){return{kind:17,content:e,tags:[["k","web"],["i",r]],created_at:Math.floor(Date.now()/1e3)}}function M(r){return v(r,"+")}function H(r){return v(r,"-")}async function P(r,e,t){const n=new f,o=r;try{const s=await n.querySync(t,{kinds:[17],authors:[e],"#k":["web"],"#i":[o],limit:1});if(s.length===0)return!1;const a=s[0];return a.content==="+"||a.content===""}catch(s){return console.error("Nostr-Components: Like button: Error checking user like status",s),!1}finally{n.close(t)}}async function T(){try{if(typeof window<"u"&&window.nostr)return await window.nostr.getPublicKey()}catch(r){console.error("Nostr-Components: Like button: Error getting user pubkey",r)}return null}async function p(r){try{if(typeof window<"u"&&window.nostr)return await window.nostr.signEvent(r);throw new Error("NIP-07 extension not available")}catch(e){throw console.error("Nostr-Components: Like button: Error signing event",e),e}}function _(){return typeof window<"u"&&!!window.nostr}class F extends x{constructor(){super();d(this,"likeActionStatus",this.channel("likeAction"));d(this,"likeListStatus",this.channel("likeList"));d(this,"currentUrl","");d(this,"isLiked",!1);d(this,"likeCount",0);d(this,"cachedLikeDetails",null);d(this,"loadSeq",0)}connectedCallback(){var t;(t=super.connectedCallback)==null||t.call(this),this.likeListStatus.get()===i.Idle&&this.initChannelStatus("likeList",i.Loading,{reflectOverall:!1}),this.attachDelegatedListeners(),this.render()}static get observedAttributes(){return[...super.observedAttributes,"url","text"]}attributeChangedCallback(t,n,o){n!==o&&(super.attributeChangedCallback(t,n,o),(t==="url"||t==="text")&&(this.likeActionStatus.set(i.Ready),this.likeListStatus.set(i.Loading),this.isLiked=!1,this.errorMessage="",this.updateLikeCount(),this.render()))}validateInputs(){if(!super.validateInputs())return this.likeActionStatus.set(i.Idle),this.likeListStatus.set(i.Idle),!1;const t=this.getAttribute("url"),n=this.getAttribute("text"),o=this.tagName.toLowerCase();let s=null;return t&&(C(t)||(s="Invalid URL format")),n&&n.length>32&&(s="Max text length: 32 characters"),s?(this.likeActionStatus.set(i.Error,s),this.likeListStatus.set(i.Error,s),console.error(`Nostr-Components: ${o}: ${s}`),!1):!0}onStatusChange(t){this.render()}onNostrRelaysConnected(){this.updateLikeCount(),this.render()}ensureCurrentUrl(){this.currentUrl||(this.currentUrl=k(this.getAttribute("url")||window.location.href))}async updateLikeCount(){const t=++this.loadSeq;try{await this.ensureNostrConnected(),this.currentUrl=k(this.getAttribute("url")||window.location.href),this.likeListStatus.set(i.Loading),this.render();const n=await I(this.currentUrl,this.getRelays());if(t!==this.loadSeq)return;this.likeCount=n.totalCount,this.cachedLikeDetails=n,this.likeListStatus.set(i.Ready)}catch(n){console.error("[NostrLike] Failed to fetch like count:",n),this.likeListStatus.set(i.Error,"Failed to load likes")}finally{this.render()}}async handleLikeClick(){if(this.ensureCurrentUrl(),!this.currentUrl){this.likeActionStatus.set(i.Error,"Invalid URL"),this.render();return}if(this.likeActionStatus.set(i.Loading),!_()){this.likeActionStatus.set(i.Error,"Please install a Nostr browser extension (Alby, nos2x, etc.)"),this.render();return}try{const t=await T();t&&(this.isLiked=await P(this.currentUrl,t,this.getRelays()))}catch(t){console.error("[NostrLike] Failed to check user like status:",t),this.likeActionStatus.set(i.Error,"Failed to check user like status")}finally{this.render()}if(this.isLiked){if(!window.confirm("You have already liked this. Do you want to unlike it?")){this.likeActionStatus.set(i.Ready),this.render();return}await this.handleUnlike()}else await this.handleLike()}async handleLike(){if(this.ensureCurrentUrl(),!this.currentUrl){this.likeActionStatus.set(i.Error,"Invalid URL"),this.render();return}this.likeActionStatus.set(i.Loading),this.render();try{const t=M(this.currentUrl),n=await p(t);await new g(this.nostrService.getNDK(),n).publish(),this.isLiked=!0,this.likeCount++,this.likeActionStatus.set(i.Ready),await this.updateLikeCount()}catch(t){console.error("[NostrLike] Failed to like:",t),this.isLiked=!1,this.likeCount--;const n=t instanceof Error?t.message:"Failed to like";this.likeActionStatus.set(i.Error,n)}finally{this.render()}}async handleUnlike(){if(this.ensureCurrentUrl(),!this.currentUrl){this.likeActionStatus.set(i.Error,"Invalid URL"),this.render();return}this.likeActionStatus.set(i.Loading),this.render();try{const t=H(this.currentUrl),n=await p(t);await new g(this.nostrService.getNDK(),n).publish(),this.isLiked=!1,this.likeCount>0&&this.likeCount--,this.likeActionStatus.set(i.Ready),await this.updateLikeCount()}catch(t){console.error("[NostrLike] Failed to unlike:",t),this.isLiked=!0,this.likeCount++;const n=t instanceof Error?t.message:"Failed to unlike";this.likeActionStatus.set(i.Error,n)}finally{this.render()}}async handleCountClick(){if(!(this.likeCount===0||!this.cachedLikeDetails))try{const{openLikersDialog:t}=await y(async()=>{const{openLikersDialog:n}=await import("../assets/dialog-likers-BjiCHFan.js");return{openLikersDialog:n}},__vite__mapDeps([0,1,2,3,4,5,6]));await t({likeDetails:this.cachedLikeDetails.likeDetails,theme:this.theme==="dark"?"dark":"light"})}catch(t){console.error("[NostrLike] Error opening likers dialog:",t)}}async handleHelpClick(){try{await $(this.theme==="dark"?"dark":"light")}catch(t){console.error("[NostrLike] Error showing help dialog:",t)}}attachDelegatedListeners(){this.delegateEvent("click",".nostr-like-button",t=>{var n,o;(n=t.preventDefault)==null||n.call(t),(o=t.stopPropagation)==null||o.call(t),this.handleLikeClick()}),this.delegateEvent("click",".like-count",t=>{var n,o;(n=t.preventDefault)==null||n.call(t),(o=t.stopPropagation)==null||o.call(t),this.handleCountClick()}),this.delegateEvent("click",".help-icon",t=>{var n,o;(n=t.preventDefault)==null||n.call(t),(o=t.stopPropagation)==null||o.call(t),this.handleHelpClick()})}renderContent(){const t=this.likeActionStatus.get()===i.Loading||this.conn.get()===i.Loading,n=this.likeListStatus.get()===i.Loading,o=this.computeOverall()===i.Error,s=this.errorMessage,a=this.getAttribute("text")||"Like",l={isLoading:t,isError:o,errorMessage:s,buttonText:a,isLiked:this.isLiked,likeCount:this.likeCount,hasLikes:this.likeCount>0,isCountLoading:n,theme:this.theme};this.shadowRoot.innerHTML=`
|
|
293
293
|
${z()}
|
|
294
294
|
${S(l)}
|
|
295
295
|
`}}customElements.define("nostr-like",F);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"mappings":";6ZAcO,SAASA,EAAiB,CAC/B,UAAAC,EACA,QAAAC,EACA,aAAAC,EACA,WAAAC,EACA,QAAAC,EACA,UAAAC,EACA,SAAAC,EAAW,GACX,eAAAC,EAAiB,GACjB,MAAAC,EAAQ,OACV,EAAoC,CAElC,GAAIP,EACK,OAAAQ,EAAYP,GAAgB,EAAE,EAGjC,MAAAQ,EAAcC,EAAgBP,EAASI,CAAK,EAC5CI,EAAcR,EAChB,qBACA,SAASS,EAAWV,CAAU,CAAC,UAEnC,OAAOW,EAAgBJ,EAAaE,EAAaP,EAAWC,EAAUF,EAASJ,EAAWO,CAAc,CAC1G,CAEA,SAASE,EAAYP,EAA8B,CAC1C,OAAAa,EACL,wCACAF,EAAWX,CAAY,CACzB,CACF,CAEA,SAASa,EAAqBC,EAAqBC,EAA8B,CACxE;AAAA;AAAA;AAAA,UAGCD,CAAW;AAAA;AAAA;AAAA,UAGXC,CAAY;AAAA;AAAA;AAAA,GAItB,CAEA,SAASH,EACPJ,EACAE,EACAP,EACAC,EAAoB,GACpBF,EAAmB,GACnBJ,EAAqB,GACrBO,EAA0B,GAClB,CACR,IAAIW,EAAY,GAChB,OAAIX,EACUW,EAAA,4CACHb,EAAY,IAErBa,EAAY,0BAA0BZ,EAAW,aAAe,EAAE,KAAKD,CAAS,IADlEA,IAAc,EAAI,OAAS,OACgD,WAMpF;AAAA;AAAA,uBAHaD,EAAU,0BAA4B,mBAK1B;AAAA,UACxBM,CAAW;AAAA,UACXV,EAAY,6CAA+CY,CAAW;AAAA;AAAA,QAExEM,CAAS;AAAA;AAAA,GAGjB,CAEA,SAASP,EAAgBP,EAAkBI,EAA0B,QAAiB,CAE9E,MAAAW,EAAaX,IAAU,OAAS,UAAY,UAC5CY,EAAeZ,IAAU,OAAS,UAAY,UAEpD,OAAIJ,EAEK;AAAA,sdAC2ce,CAAU;AAAA,gPAChPA,CAAU;AAAA,YAI/O;AAAA,oeACydC,CAAY;AAAA,8PAClPA,CAAY;AAAA,WAG1Q,CCvGO,SAASC,GAA8B,CA0N5C,OAAOC,EAzNc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAyNiB,CACxC,CC7NO,SAASC,GAA8B,CACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["renderLikeButton","isLoading","isError","errorMessage","buttonText","isLiked","likeCount","hasLikes","isCountLoading","theme","renderError","iconContent","getThumbsUpIcon","textContent","escapeHtml","renderContainer","renderErrorContainer","leftContent","rightContent","countHtml","likedColor","outlineColor","getLikeButtonStyles","getComponentStyles","getHelpDialogStyles","injectHelpDialogStyles","style","showHelpDialog","dialogComponent","normalizeURL","url","p","fetchLikesForUrl","relays","normalizedUrl","pool","SimplePool","events","likes","likedCount","dislikedCount","event","a","b","error","createReactionEvent","content","createLikeEvent","createUnlikeEvent","hasUserLiked","userPubkey","latest","getUserPubkey","signEvent","isNip07Available","NostrLike","NostrBaseComponent","__publicField","_a","NCStatus","name","oldValue","newValue","urlAttr","textAttr","tagName","isValidUrl","_status","seq","result","signedEvent","NDKEvent","openLikersDialog","__vitePreload","e","_b","renderOptions"],"ignoreList":[4],"sources":["../../src/nostr-like/render.ts","../../src/nostr-like/style.ts","../../src/nostr-like/dialog-help-style.ts","../../src/nostr-like/dialog-help.ts","../../node_modules/nostr-tools/lib/esm/utils.js","../../src/nostr-like/like-utils.ts","../../src/nostr-like/nostr-like.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport { escapeHtml } from '../common/utils';\nimport { IRenderOptions } from '../base/render-options';\n\nexport interface RenderLikeButtonOptions extends IRenderOptions {\n buttonText: string;\n isLiked: boolean;\n likeCount: number;\n hasLikes?: boolean;\n isCountLoading?: boolean;\n theme?: 'light' | 'dark';\n}\n\nexport function renderLikeButton({\n isLoading,\n isError,\n errorMessage,\n buttonText,\n isLiked,\n likeCount,\n hasLikes = false,\n isCountLoading = false,\n theme = 'light',\n}: RenderLikeButtonOptions): string {\n\n if (isError) {\n return renderError(errorMessage || '');\n }\n\n const iconContent = getThumbsUpIcon(isLiked, theme);\n const textContent = isLiked \n ? `<span>Liked</span>`\n : `<span>${escapeHtml(buttonText)}</span>`;\n\n return renderContainer(iconContent, textContent, likeCount, hasLikes, isLiked, isLoading, isCountLoading);\n}\n\nfunction renderError(errorMessage: string): string {\n return renderErrorContainer(\n '<div class=\"error-icon\">⚠</div>',\n escapeHtml(errorMessage)\n );\n}\n\nfunction renderErrorContainer(leftContent: string, rightContent: string): string {\n return `\n <div class=\"nostr-like-button-container\">\n <div class=\"nostr-like-button-left-container\">\n ${leftContent}\n </div>\n <div class=\"nostr-like-button-right-container\">\n ${rightContent}\n </div>\n </div>\n `;\n}\n\nfunction renderContainer(\n iconContent: string, \n textContent: string, \n likeCount: number, \n hasLikes: boolean = false,\n isLiked: boolean = false,\n isLoading: boolean = false,\n isCountLoading: boolean = false\n): string {\n let countHtml = '';\n if (isCountLoading) {\n countHtml = '<span class=\"like-count skeleton\"></span>';\n } else if (likeCount > 0) {\n const label = likeCount === 1 ? 'like' : 'likes';\n countHtml = `<span class=\"like-count${hasLikes ? ' clickable' : ''}\">${likeCount} ${label}</span>`;\n }\n \n const buttonClass = isLiked ? 'nostr-like-button liked' : 'nostr-like-button';\n const helpIconHtml = `<button class=\"help-icon\" title=\"What is a like?\">?</button>`;\n \n return `\n <div class=\"nostr-like-button-container\">\n <button class=\"${buttonClass}\">\n ${iconContent}\n ${isLoading ? '<span class=\"button-text-skeleton\"></span>' : textContent}\n </button>\n ${countHtml} ${helpIconHtml}\n </div>\n `;\n}\n\nfunction getThumbsUpIcon(isLiked: boolean, theme: 'light' | 'dark' = 'light'): string {\n // Determine colors based on theme\n const likedColor = theme === 'dark' ? '#8ab4f8' : '#1877f2'; // Light blue for dark theme, blue for light theme\n const outlineColor = theme === 'dark' ? '#e0e7ff' : '#0d46a1'; // Light color for dark theme, dark blue for light theme\n\n if (isLiked) {\n // Filled thumbs up\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 100 100\" width=\"24\" height=\"24\">\n <path d=\"M93.6,53.1c2.6-1.5,4.2-4.4,3.8-7.6c-0.5-4-4.2-6.8-8.2-6.8l-25,0c0.2-0.5,0.5-1.2,0.7-1.8c1.5-3.8,4.3-10.8,4.3-18 c0-8.1-5.7-13-9.6-13.3C57.2,5.5,55.4,7,55,9.7c-0.7,5.1-4.1,12.6-5.5,15.5c-0.4,0.9-0.9,1.7-1.6,2.4c-2.3,2.6-8.1,9-13.6,12.8 c0,0.2,0.1,0.5,0.1,0.7v47.9c0,0.4-0.1,0.8-0.1,1.2c9.4,2.7,17.9,4,27.2,4l21.3,0c3.7,0,7.2-2.5,7.9-6.1c0.6-3-0.5-5.7-2.5-7.5 c3.4-0.8,6-3.9,6-7.5c0-2.3-1-4.4-2.7-5.8c3.4-0.8,6-3.9,6-7.5C97.5,57,96,54.5,93.6,53.1z\" fill=\"${likedColor}\"/>\n <path d=\"M23.4,36.9H6.7c-2.3,0-4.2,1.9-4.2,4.2v47.9c0,2.3,1.9,4.2,4.2,4.2h16.7c2.3,0,4.2-1.9,4.2-4.2V41.2 C27.6,38.8,25.8,36.9,23.4,36.9z M15.1,85.9c-2.4,0-4.4-2-4.4-4.4s2-4.4,4.4-4.4c2.4,0,4.4,2,4.4,4.4S17.5,85.9,15.1,85.9z\" fill=\"${likedColor}\"/>\n </svg>`;\n } else {\n // Outline thumbs up\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 100 100\" width=\"24\" height=\"24\">\n <path d=\"M93.6,53.1c2.6-1.5,4.2-4.4,3.8-7.6c-0.5-4-4.2-6.8-8.2-6.8l-25,0c0.2-0.5,0.5-1.2,0.7-1.8c1.5-3.8,4.3-10.8,4.3-18 c0-8.1-5.7-13-9.6-13.3C57.2,5.5,55.4,7,55,9.7c-0.7,5.1-4.1,12.6-5.5,15.5c-0.4,0.9-0.9,1.7-1.6,2.4c-2.3,2.6-8.1,9-13.6,12.8 c0,0.2,0.1,0.5,0.1,0.7v47.9c0,0.4-0.1,0.8-0.1,1.2c9.4,2.7,17.9,4,27.2,4l21.3,0c3.7,0,7.2-2.5,7.9-6.1c0.6-3-0.5-5.7-2.5-7.5 c3.4-0.8,6-3.9,6-7.5c0-2.3-1-4.4-2.7-5.8c3.4-0.8,6-3.9,6-7.5C97.5,57,96,54.5,93.6,53.1z\" fill=\"none\" stroke=\"${outlineColor}\" stroke-width=\"2\"/>\n <path d=\"M23.4,36.9H6.7c-2.3,0-4.2,1.9-4.2,4.2v47.9c0,2.3,1.9,4.2,4.2,4.2h16.7c2.3,0,4.2-1.9,4.2-4.2V41.2 C27.6,38.8,25.8,36.9,23.4,36.9z M15.1,85.9c-2.4,0-4.4-2-4.4-4.4s2-4.4,4.4-4.4c2.4,0,4.4,2,4.4,4.4S17.5,85.9,15.1,85.9z\" fill=\"none\" stroke=\"${outlineColor}\" stroke-width=\"2\"/>\n </svg>`;\n }\n}\n","// SPDX-License-Identifier: MIT\n\nimport { getComponentStyles } from '../common/base-styles';\n\nexport function getLikeButtonStyles(): string {\n const customStyles = `\n /* === LIKE BUTTON CONTAINER PATTERN === */\n :host {\n /* Icon sizing (overridable via CSS variables) */\n --nostrc-icon-height: 25px;\n --nostrc-icon-width: 25px;\n\n /* Like button CSS variables (overridable by parent components) */\n --nostrc-like-btn-padding: var(--nostrc-spacing-sm) var(--nostrc-spacing-md);\n --nostrc-like-btn-border-radius: var(--nostrc-border-radius-md);\n --nostrc-like-btn-border: var(--nostrc-border-width) solid var(--nostrc-color-border);\n --nostrc-like-btn-min-height: 47px;\n --nostrc-like-btn-width: auto;\n --nostrc-like-btn-horizontal-alignment: left;\n --nostrc-like-btn-bg: var(--nostrc-theme-bg, #ffffff);\n --nostrc-like-btn-color: var(--nostrc-theme-text-primary, #333333);\n --nostrc-like-btn-font-family: var(--nostrc-font-family-primary);\n --nostrc-like-btn-font-size: var(--nostrc-font-size-base);\n \n /* Hover state variables */\n --nostrc-like-btn-hover-bg: var(--nostrc-theme-hover-bg, rgba(0, 0, 0, 0.05));\n --nostrc-like-btn-hover-color: var(--nostrc-theme-text-primary, #333333);\n --nostrc-like-btn-hover-border: var(--nostrc-border-width) solid var(--nostrc-theme-border, var(--nostrc-color-border));\n\n /* Liked state variables */\n --nostrc-like-btn-liked-bg: #e7f3ff;\n --nostrc-like-btn-liked-color: #1877f2;\n --nostrc-like-btn-liked-border: #1877f2;\n --nostrc-like-btn-liked-hover-bg: #d1e7ff;\n\n /* Make the host a flex container for button + count */\n display: inline-flex;\n flex-direction: row;\n align-items: center;\n gap: var(--nostrc-spacing-md);\n font-family: var(--nostrc-like-btn-font-family);\n font-size: var(--nostrc-like-btn-font-size);\n }\n\n /* Focus state for accessibility */\n :host(:focus-visible) {\n outline: 2px solid var(--nostrc-color-primary, #007bff);\n outline-offset: 2px;\n }\n\n :host(.is-error) .nostr-like-button-container {\n border: var(--nostrc-border-width) solid var(--nostrc-color-error-text);\n border-radius: var(--nostrc-border-radius-md);\n padding: var(--nostrc-spacing-sm);\n color: var(--nostrc-color-error-text);\n }\n\n .nostr-like-button-container {\n display: flex;\n align-items: center;\n gap: var(--nostrc-spacing-md);\n width: fit-content;\n }\n\n .nostr-like-button-left-container {\n display: flex;\n align-items: center;\n }\n\n .nostr-like-button-right-container {\n display: flex;\n align-items: center;\n }\n\n .nostr-like-button {\n display: flex;\n align-items: center;\n justify-content: var(--nostrc-like-btn-horizontal-alignment);\n gap: var(--nostrc-spacing-sm);\n background: var(--nostrc-like-btn-bg);\n color: var(--nostrc-like-btn-color);\n border: var(--nostrc-like-btn-border);\n border-radius: var(--nostrc-like-btn-border-radius);\n padding: var(--nostrc-like-btn-padding);\n min-height: var(--nostrc-like-btn-min-height);\n width: var(--nostrc-like-btn-width);\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;\n font-family: inherit;\n font-size: inherit;\n }\n\n /* Hover state on the button */\n .nostr-like-button:hover {\n background: var(--nostrc-like-btn-hover-bg);\n color: var(--nostrc-like-btn-hover-color);\n border: var(--nostrc-like-btn-hover-border);\n }\n\n /* Liked state */\n .nostr-like-button.liked {\n background: var(--nostrc-like-btn-liked-bg);\n color: var(--nostrc-like-btn-liked-color);\n border: var(--nostrc-border-width) solid var(--nostrc-like-btn-liked-border);\n }\n\n .nostr-like-button.liked:hover {\n background: var(--nostrc-like-btn-liked-hover-bg);\n }\n\n .nostr-like-button:disabled {\n pointer-events: none;\n user-select: none;\n opacity: 0.6;\n }\n\n :host:not([status=\"ready\"]) .nostr-like-button {\n cursor: not-allowed;\n }\n\n /* SVG Icon Styles */\n .nostr-like-button svg {\n display: inline-block;\n vertical-align: middle;\n width: var(--nostrc-icon-width);\n height: var(--nostrc-icon-height);\n }\n\n /* Like count display */\n .like-count {\n font-size: var(--nostrc-font-size-sm);\n color: var(--nostrc-theme-text-secondary, #666666);\n white-space: nowrap;\n text-decoration: underline;\n text-decoration-color: transparent;\n transition: text-decoration-color 0.2s ease, color 0.2s ease;\n }\n\n /* Clickable like count */\n .like-count.clickable {\n cursor: pointer;\n text-decoration-color: currentColor;\n }\n\n .like-count.clickable:hover {\n color: var(--nostrc-color-primary, #7f00ff);\n text-decoration-color: var(--nostrc-color-primary, #7f00ff);\n }\n\n /* Help icon */\n .help-icon {\n background: none;\n border: 1px solid var(--nostrc-color-border, #e0e0e0);\n border-radius: var(--nostrc-border-radius-full, 50%);\n width: var(--nostrc-help-icon-size, 16px);\n height: var(--nostrc-help-icon-size, 16px);\n font-size: calc(var(--nostrc-help-icon-size, 16px) * 0.7);\n font-weight: bold;\n color: var(--nostrc-theme-text-secondary, #666666);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n margin-left: var(--nostrc-spacing-xs, 4px);\n transition: all 0.2s ease;\n }\n\n .help-icon:hover {\n background: var(--nostrc-color-hover-background, rgba(0, 0, 0, 0.05));\n border-color: var(--nostrc-color-primary, #7f00ff);\n color: var(--nostrc-color-primary, #7f00ff);\n }\n\n /* Skeleton loader for like count */\n .like-count.skeleton {\n background: linear-gradient(90deg, \n var(--nostrc-skeleton-color-min) 25%, \n var(--nostrc-skeleton-color-max) 50%, \n var(--nostrc-skeleton-color-min) 75%\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 width: 80px;\n height: 1.2em;\n display: inline-block;\n }\n\n /* Skeleton loader for button text */\n .button-text-skeleton {\n background: linear-gradient(90deg, \n var(--nostrc-skeleton-color-min) 25%, \n var(--nostrc-skeleton-color-max) 50%, \n var(--nostrc-skeleton-color-min) 75%\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 width: 60px;\n height: 1em;\n display: inline-block;\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 /* Error message styling */\n .nostr-like-button-error small {\n color: var(--nostrc-color-error-text);\n font-size: var(--nostrc-font-size-sm);\n line-height: 1em;\n max-width: 250px;\n white-space: pre-line;\n }\n `;\n \n return getComponentStyles(customStyles);\n}\n","// SPDX-License-Identifier: MIT\n\nexport function getHelpDialogStyles(): string {\n return `\n .help-content {\n padding: var(--nostrc-spacing-md, 12px);\n }\n\n .help-content p {\n margin: 0 0 var(--nostrc-spacing-md, 12px) 0;\n color: var(--nostrc-theme-text-primary, #333333);\n line-height: 1.5;\n }\n\n .help-content p:last-child {\n margin-bottom: 0;\n }\n\n .help-content ul {\n margin: 0 0 var(--nostrc-spacing-md, 12px) 0;\n padding-left: var(--nostrc-spacing-lg, 16px);\n color: var(--nostrc-theme-text-primary, #333333);\n }\n\n .help-content li {\n margin-bottom: var(--nostrc-spacing-xs, 4px);\n line-height: 1.5;\n }\n\n .help-content li:last-child {\n margin-bottom: 0;\n }\n\n .help-content strong {\n font-weight: 600;\n color: var(--nostrc-theme-text-primary, #333333);\n }\n\n .help-content a {\n color: var(--nostrc-theme-primary, #0066cc);\n text-decoration: underline;\n }\n\n .help-content a:hover {\n color: var(--nostrc-theme-primary-hover, #0052a3);\n }\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 { getHelpDialogStyles } from './dialog-help-style';\n\nexport const injectHelpDialogStyles = (): void => {\n // Check if styles are already injected\n if (document.querySelector('style[data-help-dialog-styles]')) return;\n \n const style = document.createElement('style');\n style.setAttribute('data-help-dialog-styles', 'true');\n style.textContent = getHelpDialogStyles();\n document.head.appendChild(style);\n};\n\nexport const showHelpDialog = async (theme?: 'light' | 'dark'): Promise<void> => {\n injectHelpDialogStyles();\n \n if (!customElements.get('dialog-component')) {\n await customElements.whenDefined('dialog-component');\n }\n \n const dialogComponent = document.createElement('dialog-component') as DialogComponent;\n dialogComponent.setAttribute('header', 'What is a Like?');\n if (theme) {\n dialogComponent.setAttribute('data-theme', theme);\n }\n \n // Set dialog content\n dialogComponent.innerHTML = `\n <div class=\"help-content\">\n <p>Like any webpage to show your appreciation! Your likes are stored on Nostr, a decentralized network you control—no accounts needed.</p>\n <ul>\n <li>Like any webpage or article</li>\n <li>See who liked the content</li>\n <li>Works with a browser extension like <a href=\"https://getalby.com\" target=\"_blank\" rel=\"noopener noreferrer\">Alby</a> or nos2x</li>\n </ul>\n </div>\n `;\n \n dialogComponent.showModal();\n};\n\n","// utils.ts\nvar utf8Decoder = new TextDecoder(\"utf-8\");\nvar utf8Encoder = new TextEncoder();\nfunction normalizeURL(url) {\n if (url.indexOf(\"://\") === -1)\n url = \"wss://\" + url;\n let p = new URL(url);\n p.pathname = p.pathname.replace(/\\/+/g, \"/\");\n if (p.pathname.endsWith(\"/\"))\n p.pathname = p.pathname.slice(0, -1);\n if (p.port === \"80\" && p.protocol === \"ws:\" || p.port === \"443\" && p.protocol === \"wss:\")\n p.port = \"\";\n p.searchParams.sort();\n p.hash = \"\";\n return p.toString();\n}\nfunction insertEventIntoDescendingList(sortedArray, event) {\n const [idx, found] = binarySearch(sortedArray, (b) => {\n if (event.id === b.id)\n return 0;\n if (event.created_at === b.created_at)\n return -1;\n return b.created_at - event.created_at;\n });\n if (!found) {\n sortedArray.splice(idx, 0, event);\n }\n return sortedArray;\n}\nfunction insertEventIntoAscendingList(sortedArray, event) {\n const [idx, found] = binarySearch(sortedArray, (b) => {\n if (event.id === b.id)\n return 0;\n if (event.created_at === b.created_at)\n return -1;\n return event.created_at - b.created_at;\n });\n if (!found) {\n sortedArray.splice(idx, 0, event);\n }\n return sortedArray;\n}\nfunction binarySearch(arr, compare) {\n let start = 0;\n let end = arr.length - 1;\n while (start <= end) {\n const mid = Math.floor((start + end) / 2);\n const cmp = compare(arr[mid]);\n if (cmp === 0) {\n return [mid, true];\n }\n if (cmp < 0) {\n end = mid - 1;\n } else {\n start = mid + 1;\n }\n }\n return [start, false];\n}\nvar QueueNode = class {\n value;\n next = null;\n prev = null;\n constructor(message) {\n this.value = message;\n }\n};\nvar Queue = class {\n first;\n last;\n constructor() {\n this.first = null;\n this.last = null;\n }\n enqueue(value) {\n const newNode = new QueueNode(value);\n if (!this.last) {\n this.first = newNode;\n this.last = newNode;\n } else if (this.last === this.first) {\n this.last = newNode;\n this.last.prev = this.first;\n this.first.next = newNode;\n } else {\n newNode.prev = this.last;\n this.last.next = newNode;\n this.last = newNode;\n }\n return true;\n }\n dequeue() {\n if (!this.first)\n return null;\n if (this.first === this.last) {\n const target2 = this.first;\n this.first = null;\n this.last = null;\n return target2.value;\n }\n const target = this.first;\n this.first = target.next;\n return target.value;\n }\n};\nexport {\n Queue,\n QueueNode,\n binarySearch,\n insertEventIntoAscendingList,\n insertEventIntoDescendingList,\n normalizeURL,\n utf8Decoder,\n utf8Encoder\n};\n","// SPDX-License-Identifier: MIT\n\nimport { SimplePool } from 'nostr-tools';\nimport { normalizeURL } from 'nostr-tools/utils';\n\n/**\n * Helper utilities for Nostr like operations using NIP-25 External Content Reactions.\n * These are deliberately kept self-contained so `nostr-like` Web Component can import\n * everything from a single module without polluting the rest of the codebase.\n */\n\nexport interface LikeDetails {\n authorPubkey: string;\n date: Date;\n content: string;\n}\n\nexport interface LikeCountResult {\n totalCount: number;\n likeDetails: LikeDetails[];\n likedCount: number;\n dislikedCount: number;\n}\n\n/**\n * Fetch all likes for a URL using NIP-25 kind 17 events\n */\nexport async function fetchLikesForUrl(\n url: string, \n relays: string[]\n): Promise<LikeCountResult> {\n // Normalize URL at the beginning for consistent comparison with tags\n const normalizedUrl = normalizeURL(url);\n \n const pool = new SimplePool();\n \n try {\n // Query kind 17 events (both likes and unlikes)\n const events = await pool.querySync(relays, {\n kinds: [17],\n '#k': ['web'],\n '#i': [normalizedUrl],\n limit: 1000\n });\n \n const likes: LikeDetails[] = [];\n let likedCount = 0;\n let dislikedCount = 0;\n \n for (const event of events) {\n likes.push({\n authorPubkey: event.pubkey,\n date: new Date(event.created_at * 1000),\n content: event.content\n });\n \n if (event.content === '-') {\n dislikedCount++;\n } else {\n likedCount++;\n }\n }\n \n likes.sort((a, b) => b.date.getTime() - a.date.getTime());\n \n const totalCount = likedCount - dislikedCount;\n \n return {\n totalCount: totalCount,\n likeDetails: likes,\n likedCount: likedCount,\n dislikedCount: dislikedCount\n };\n } catch (error) {\n // Rethrow error so callers can handle relay/network failures appropriately\n throw error instanceof Error ? error : new Error(String(error));\n } finally {\n pool.close(relays);\n }\n}\n\n/**\n * Create reaction event (kind 17)\n * @param url - URL to react to\n * @param content - '+' for like, '-' for unlike\n */\nexport function createReactionEvent(url: string, content: '+' | '-'): any {\n return {\n kind: 17,\n content,\n tags: [\n ['k', 'web'],\n ['i', url]\n ],\n created_at: Math.floor(Date.now() / 1000)\n };\n}\n\n/**\n * Create like event (kind 17)\n * @deprecated Use createReactionEvent(url, '+') instead\n */\nexport function createLikeEvent(url: string): any {\n return createReactionEvent(url, '+');\n}\n\n/**\n * Create unlike event (kind 17 with '-' content)\n * @deprecated Use createReactionEvent(url, '-') instead\n */\nexport function createUnlikeEvent(url: string): any {\n return createReactionEvent(url, '-');\n}\n\n/**\n * Check if user has liked a URL\n */\nexport async function hasUserLiked(\n url: string,\n userPubkey: string,\n relays: string[]\n): Promise<boolean> {\n const pool = new SimplePool();\n const normalizedUrl = url;\n \n try {\n // Get user's latest reaction for this URL\n const events = await pool.querySync(relays, {\n kinds: [17],\n authors: [userPubkey],\n '#k': ['web'],\n '#i': [normalizedUrl],\n limit: 1\n });\n \n if (events.length === 0) return false;\n \n // Check if latest reaction is a like (not an unlike)\n const latest = events[0];\n return latest.content === '+' || latest.content === '';\n } catch (error) {\n console.error(\"Nostr-Components: Like button: Error checking user like status\", error);\n return false;\n } finally {\n pool.close(relays);\n }\n}\n\n/**\n * Get user's pubkey from NIP-07 signer\n */\nexport async function getUserPubkey(): Promise<string | null> {\n try {\n if (typeof window !== 'undefined' && (window as any).nostr) {\n const nip07signer = (window as any).nostr;\n const user = await nip07signer.getPublicKey();\n return user;\n }\n } catch (error) {\n console.error(\"Nostr-Components: Like button: Error getting user pubkey\", error);\n }\n return null;\n}\n\n/**\n * Sign event with NIP-07\n */\nexport async function signEvent(event: any): Promise<any> {\n try {\n if (typeof window !== 'undefined' && (window as any).nostr) {\n const nip07signer = (window as any).nostr;\n const signedEvent = await nip07signer.signEvent(event);\n return signedEvent;\n }\n throw new Error('NIP-07 extension not available');\n } catch (error) {\n console.error(\"Nostr-Components: Like button: Error signing event\", error);\n throw error;\n }\n}\n\n/**\n * Check if NIP-07 extension is available\n */\nexport function isNip07Available(): boolean {\n return typeof window !== 'undefined' && !!(window as any).nostr;\n}\n","// SPDX-License-Identifier: MIT\n\nimport { NostrBaseComponent } from '../base/base-component/nostr-base-component';\nimport { NCStatus } from '../base/base-component/nostr-base-component';\nimport { NDKEvent } from '@nostr-dev-kit/ndk';\nimport { renderLikeButton, RenderLikeButtonOptions } from './render';\nimport { getLikeButtonStyles } from './style';\nimport { showHelpDialog } from './dialog-help';\nimport { isValidUrl } from '../common/utils';\nimport { \n fetchLikesForUrl, \n createLikeEvent,\n createUnlikeEvent,\n hasUserLiked, \n getUserPubkey, \n signEvent, \n isNip07Available,\n LikeCountResult \n} from './like-utils';\nimport { normalizeURL } from 'nostr-tools/utils';\n\n/**\n * <nostr-like>\n * Attributes:\n * - url (optional) : URL to like (default: current page URL)\n * - text (optional) : custom text (default \"Like\") (Max 32 characters)\n * - relays (optional) : comma-separated relay URLs\n * - data-theme (optional) : \"light\" | \"dark\" (default light)\n * \n * Features:\n * - URL-based likes using NIP-25 kind 17 events\n * - Click count to view likers\n */\nexport default class NostrLike extends NostrBaseComponent {\n protected likeActionStatus = this.channel('likeAction');\n protected likeListStatus = this.channel('likeList');\n \n private currentUrl: string = '';\n private isLiked: boolean = false;\n private likeCount: number = 0;\n private cachedLikeDetails: LikeCountResult | null = null;\n private loadSeq = 0;\n\n constructor() {\n super();\n }\n\n connectedCallback() {\n super.connectedCallback?.();\n if (this.likeListStatus.get() === NCStatus.Idle) {\n this.likeListStatus.set(NCStatus.Loading);\n }\n this.attachDelegatedListeners();\n this.render();\n }\n\n static get observedAttributes() {\n return [\n ...super.observedAttributes,\n 'url',\n 'text'\n ];\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null\n ) {\n if (oldValue === newValue) return;\n super.attributeChangedCallback(name, oldValue, newValue);\n \n if (name === 'url' || name === 'text') {\n this.likeActionStatus.set(NCStatus.Ready);\n this.likeListStatus.set(NCStatus.Loading);\n this.isLiked = false;\n this.errorMessage = '';\n this.updateLikeCount();\n this.render();\n }\n }\n\n /** Base class functions */\n protected validateInputs(): boolean {\n if (!super.validateInputs()) {\n this.likeActionStatus.set(NCStatus.Idle);\n this.likeListStatus.set(NCStatus.Idle);\n return false;\n }\n\n const urlAttr = this.getAttribute('url');\n const textAttr = this.getAttribute('text');\n const tagName = this.tagName.toLowerCase();\n\n let errorMessage: string | null = null;\n\n if (urlAttr) {\n if (!isValidUrl(urlAttr)) {\n errorMessage = 'Invalid URL format';\n }\n }\n\n if (textAttr && textAttr.length > 32) {\n errorMessage = 'Max text length: 32 characters';\n }\n\n if (errorMessage) {\n this.likeActionStatus.set(NCStatus.Error, errorMessage);\n this.likeListStatus.set(NCStatus.Error, errorMessage);\n console.error(`Nostr-Components: ${tagName}: ${errorMessage}`);\n return false;\n }\n\n return true;\n }\n\n protected onStatusChange(_status: NCStatus) {\n this.render();\n }\n\n protected onNostrRelaysConnected() {\n this.updateLikeCount();\n this.render();\n }\n\n /** Private functions */\n /**\n * Lazy initializer for currentUrl - ensures it's set before like/unlike operations\n */\n private ensureCurrentUrl(): void {\n if (!this.currentUrl) {\n this.currentUrl = normalizeURL(this.getAttribute('url') || window.location.href);\n }\n }\n\n private async updateLikeCount() {\n const seq = ++this.loadSeq;\n try {\n await this.ensureNostrConnected();\n this.currentUrl = normalizeURL(this.getAttribute('url') || window.location.href);\n this.likeListStatus.set(NCStatus.Loading);\n this.render();\n \n const result = await fetchLikesForUrl(this.currentUrl, this.getRelays());\n if (seq !== this.loadSeq) return; // stale\n this.likeCount = result.totalCount;\n this.cachedLikeDetails = result;\n this.likeListStatus.set(NCStatus.Ready);\n } catch (error) {\n console.error('[NostrLike] Failed to fetch like count:', error);\n this.likeListStatus.set(NCStatus.Error, 'Failed to load likes');\n } finally {\n this.render();\n }\n }\n\n // TODO: Do onboarding logic here\n private async handleLikeClick() {\n // Ensure currentUrl is set before proceeding\n this.ensureCurrentUrl();\n \n if (!this.currentUrl) {\n this.likeActionStatus.set(NCStatus.Error, 'Invalid URL');\n this.render();\n return;\n }\n\n this.likeActionStatus.set(NCStatus.Loading);\n if (!isNip07Available()) {\n this.likeActionStatus.set(NCStatus.Error, \n 'Please install a Nostr browser extension (Alby, nos2x, etc.)'\n );\n this.render();\n return;\n }\n\n // Check user like status\n try {\n const userPubkey = await getUserPubkey();\n if (userPubkey) {\n this.isLiked = await hasUserLiked(this.currentUrl, userPubkey, this.getRelays());\n }\n } catch (error) {\n console.error('[NostrLike] Failed to check user like status:', error);\n this.likeActionStatus.set(NCStatus.Error, 'Failed to check user like status');\n } finally {\n this.render();\n }\n\n // If already liked, show confirmation dialog\n if (this.isLiked) {\n const confirmed = window.confirm('You have already liked this. Do you want to unlike it?');\n if (!confirmed) {\n this.likeActionStatus.set(NCStatus.Ready);\n this.render();\n return;\n }\n \n // Proceed with unlike\n await this.handleUnlike();\n } else {\n // Proceed with like\n await this.handleLike();\n }\n }\n\n private async handleLike() {\n // Ensure currentUrl is set before proceeding\n this.ensureCurrentUrl();\n \n if (!this.currentUrl) {\n this.likeActionStatus.set(NCStatus.Error, 'Invalid URL');\n this.render();\n return;\n }\n\n this.likeActionStatus.set(NCStatus.Loading);\n this.render();\n\n try {\n // Create like event\n const event = createLikeEvent(this.currentUrl);\n \n // Sign with NIP-07\n const signedEvent = await signEvent(event);\n \n // Create NDKEvent and publish\n const ndkEvent = new NDKEvent(this.nostrService.getNDK(), signedEvent);\n await ndkEvent.publish();\n \n // Update state optimistically\n this.isLiked = true;\n this.likeCount++;\n this.likeActionStatus.set(NCStatus.Ready);\n \n // Refresh like count to get accurate data\n await this.updateLikeCount();\n } catch (error) {\n console.error('[NostrLike] Failed to like:', error);\n \n // Rollback optimistic update\n this.isLiked = false;\n this.likeCount--;\n \n const errorMessage = error instanceof Error ? error.message : 'Failed to like';\n this.likeActionStatus.set(NCStatus.Error, errorMessage);\n } finally {\n this.render();\n }\n }\n\n private async handleUnlike() {\n // Ensure currentUrl is set before proceeding\n this.ensureCurrentUrl();\n \n if (!this.currentUrl) {\n this.likeActionStatus.set(NCStatus.Error, 'Invalid URL');\n this.render();\n return;\n }\n\n this.likeActionStatus.set(NCStatus.Loading);\n this.render();\n \n try {\n // Create unlike event\n const event = createUnlikeEvent(this.currentUrl);\n \n // Sign with NIP-07\n const signedEvent = await signEvent(event);\n \n // Create NDKEvent and publish\n const ndkEvent = new NDKEvent(this.nostrService.getNDK(), signedEvent);\n await ndkEvent.publish();\n \n // Update state optimistically\n this.isLiked = false;\n if (this.likeCount > 0) {\n this.likeCount--;\n }\n this.likeActionStatus.set(NCStatus.Ready);\n \n // Refresh like count to get accurate data\n await this.updateLikeCount();\n } catch (error) {\n console.error('[NostrLike] Failed to unlike:', error);\n \n // Rollback optimistic update\n this.isLiked = true;\n this.likeCount++;\n \n const errorMessage = error instanceof Error ? error.message : 'Failed to unlike';\n this.likeActionStatus.set(NCStatus.Error, errorMessage);\n } finally {\n this.render();\n }\n }\n\n private async handleCountClick() {\n if (this.likeCount === 0 || !this.cachedLikeDetails) {\n return;\n }\n\n try {\n // Import dialog dynamically to avoid circular dependencies\n const { openLikersDialog } = await import('./dialog-likers');\n await openLikersDialog({\n likeDetails: this.cachedLikeDetails.likeDetails,\n theme: this.theme === 'dark' ? 'dark' : 'light',\n });\n } catch (error) {\n console.error('[NostrLike] Error opening likers dialog:', error);\n }\n }\n\n private async handleHelpClick() {\n try {\n await showHelpDialog(this.theme === 'dark' ? 'dark' : 'light');\n } catch (error) {\n console.error('[NostrLike] Error showing help dialog:', error);\n }\n }\n\n private attachDelegatedListeners() {\n this.delegateEvent('click', '.nostr-like-button', (e) => {\n e.preventDefault?.();\n e.stopPropagation?.();\n void this.handleLikeClick();\n });\n\n this.delegateEvent('click', '.like-count', (e) => {\n e.preventDefault?.();\n e.stopPropagation?.();\n void this.handleCountClick();\n });\n\n this.delegateEvent('click', '.help-icon', (e) => {\n e.preventDefault?.();\n e.stopPropagation?.();\n this.handleHelpClick();\n });\n }\n\n protected renderContent() {\n // console.log(`Like: Render: conn: ${this.conn.get()}, likeActionStatus: ${this.likeActionStatus.get()}, likeListStatus: ${this.likeListStatus.get()}`);\n const isLoading = this.likeActionStatus.get() === NCStatus.Loading || this.conn.get() === NCStatus.Loading;\n const isCountLoading = this.likeListStatus.get() === NCStatus.Loading;\n const isError = this.computeOverall() === NCStatus.Error;\n const errorMessage = this.errorMessage;\n const buttonText = this.getAttribute('text') || 'Like';\n\n const renderOptions: RenderLikeButtonOptions = {\n isLoading,\n isError,\n errorMessage,\n buttonText,\n isLiked: this.isLiked,\n likeCount: this.likeCount,\n hasLikes: this.likeCount > 0,\n isCountLoading,\n theme: this.theme as 'light' | 'dark',\n };\n\n this.shadowRoot!.innerHTML = `\n ${getLikeButtonStyles()}\n ${renderLikeButton(renderOptions)}\n `;\n }\n}\n\ncustomElements.define('nostr-like', NostrLike);\n"],"file":"components/nostr-like.es.js"}
|
|
1
|
+
{"version":3,"mappings":";6ZAcO,SAASA,EAAiB,CAC/B,UAAAC,EACA,QAAAC,EACA,aAAAC,EACA,WAAAC,EACA,QAAAC,EACA,UAAAC,EACA,SAAAC,EAAW,GACX,eAAAC,EAAiB,GACjB,MAAAC,EAAQ,OACV,EAAoC,CAElC,GAAIP,EACK,OAAAQ,EAAYP,GAAgB,EAAE,EAGjC,MAAAQ,EAAcC,EAAgBP,EAASI,CAAK,EAC5CI,EAAcR,EAChB,qBACA,SAASS,EAAWV,CAAU,CAAC,UAEnC,OAAOW,EAAgBJ,EAAaE,EAAaP,EAAWC,EAAUF,EAASJ,EAAWO,CAAc,CAC1G,CAEA,SAASE,EAAYP,EAA8B,CAC1C,OAAAa,EACL,wCACAF,EAAWX,CAAY,CACzB,CACF,CAEA,SAASa,EAAqBC,EAAqBC,EAA8B,CACxE;AAAA;AAAA;AAAA,UAGCD,CAAW;AAAA;AAAA;AAAA,UAGXC,CAAY;AAAA;AAAA;AAAA,GAItB,CAEA,SAASH,EACPJ,EACAE,EACAP,EACAC,EAAoB,GACpBF,EAAmB,GACnBJ,EAAqB,GACrBO,EAA0B,GAClB,CACR,IAAIW,EAAY,GAChB,OAAIX,EACUW,EAAA,4CACHb,EAAY,IAErBa,EAAY,0BAA0BZ,EAAW,aAAe,EAAE,KAAKD,CAAS,IADlEA,IAAc,EAAI,OAAS,OACgD,WAMpF;AAAA;AAAA,uBAHaD,EAAU,0BAA4B,mBAK1B;AAAA,UACxBM,CAAW;AAAA,UACXV,EAAY,6CAA+CY,CAAW;AAAA;AAAA,QAExEM,CAAS;AAAA;AAAA,GAGjB,CAEA,SAASP,EAAgBP,EAAkBI,EAA0B,QAAiB,CAE9E,MAAAW,EAAaX,IAAU,OAAS,UAAY,UAC5CY,EAAeZ,IAAU,OAAS,UAAY,UAEpD,OAAIJ,EAEK;AAAA,sdAC2ce,CAAU;AAAA,gPAChPA,CAAU;AAAA,YAI/O;AAAA,oeACydC,CAAY;AAAA,8PAClPA,CAAY;AAAA,WAG1Q,CCvGO,SAASC,GAA8B,CA0N5C,OAAOC,EAzNc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAyNiB,CACxC,CC7NO,SAASC,GAA8B,CACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["renderLikeButton","isLoading","isError","errorMessage","buttonText","isLiked","likeCount","hasLikes","isCountLoading","theme","renderError","iconContent","getThumbsUpIcon","textContent","escapeHtml","renderContainer","renderErrorContainer","leftContent","rightContent","countHtml","likedColor","outlineColor","getLikeButtonStyles","getComponentStyles","getHelpDialogStyles","injectHelpDialogStyles","style","showHelpDialog","dialogComponent","normalizeURL","url","p","fetchLikesForUrl","relays","normalizedUrl","pool","SimplePool","events","likes","likedCount","dislikedCount","event","a","b","error","createReactionEvent","content","createLikeEvent","createUnlikeEvent","hasUserLiked","userPubkey","latest","getUserPubkey","signEvent","isNip07Available","NostrLike","NostrBaseComponent","__publicField","_a","NCStatus","name","oldValue","newValue","urlAttr","textAttr","tagName","isValidUrl","_status","seq","result","signedEvent","NDKEvent","openLikersDialog","__vitePreload","e","_b","renderOptions"],"ignoreList":[4],"sources":["../../src/nostr-like/render.ts","../../src/nostr-like/style.ts","../../src/nostr-like/dialog-help-style.ts","../../src/nostr-like/dialog-help.ts","../../node_modules/nostr-tools/lib/esm/utils.js","../../src/nostr-like/like-utils.ts","../../src/nostr-like/nostr-like.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport { escapeHtml } from '../common/utils';\nimport { IRenderOptions } from '../base/render-options';\n\nexport interface RenderLikeButtonOptions extends IRenderOptions {\n buttonText: string;\n isLiked: boolean;\n likeCount: number;\n hasLikes?: boolean;\n isCountLoading?: boolean;\n theme?: 'light' | 'dark';\n}\n\nexport function renderLikeButton({\n isLoading,\n isError,\n errorMessage,\n buttonText,\n isLiked,\n likeCount,\n hasLikes = false,\n isCountLoading = false,\n theme = 'light',\n}: RenderLikeButtonOptions): string {\n\n if (isError) {\n return renderError(errorMessage || '');\n }\n\n const iconContent = getThumbsUpIcon(isLiked, theme);\n const textContent = isLiked \n ? `<span>Liked</span>`\n : `<span>${escapeHtml(buttonText)}</span>`;\n\n return renderContainer(iconContent, textContent, likeCount, hasLikes, isLiked, isLoading, isCountLoading);\n}\n\nfunction renderError(errorMessage: string): string {\n return renderErrorContainer(\n '<div class=\"error-icon\">⚠</div>',\n escapeHtml(errorMessage)\n );\n}\n\nfunction renderErrorContainer(leftContent: string, rightContent: string): string {\n return `\n <div class=\"nostr-like-button-container\">\n <div class=\"nostr-like-button-left-container\">\n ${leftContent}\n </div>\n <div class=\"nostr-like-button-right-container\">\n ${rightContent}\n </div>\n </div>\n `;\n}\n\nfunction renderContainer(\n iconContent: string, \n textContent: string, \n likeCount: number, \n hasLikes: boolean = false,\n isLiked: boolean = false,\n isLoading: boolean = false,\n isCountLoading: boolean = false\n): string {\n let countHtml = '';\n if (isCountLoading) {\n countHtml = '<span class=\"like-count skeleton\"></span>';\n } else if (likeCount > 0) {\n const label = likeCount === 1 ? 'like' : 'likes';\n countHtml = `<span class=\"like-count${hasLikes ? ' clickable' : ''}\">${likeCount} ${label}</span>`;\n }\n \n const buttonClass = isLiked ? 'nostr-like-button liked' : 'nostr-like-button';\n const helpIconHtml = `<button class=\"help-icon\" title=\"What is a like?\">?</button>`;\n \n return `\n <div class=\"nostr-like-button-container\">\n <button class=\"${buttonClass}\">\n ${iconContent}\n ${isLoading ? '<span class=\"button-text-skeleton\"></span>' : textContent}\n </button>\n ${countHtml} ${helpIconHtml}\n </div>\n `;\n}\n\nfunction getThumbsUpIcon(isLiked: boolean, theme: 'light' | 'dark' = 'light'): string {\n // Determine colors based on theme\n const likedColor = theme === 'dark' ? '#8ab4f8' : '#1877f2'; // Light blue for dark theme, blue for light theme\n const outlineColor = theme === 'dark' ? '#e0e7ff' : '#0d46a1'; // Light color for dark theme, dark blue for light theme\n\n if (isLiked) {\n // Filled thumbs up\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 100 100\" width=\"24\" height=\"24\">\n <path d=\"M93.6,53.1c2.6-1.5,4.2-4.4,3.8-7.6c-0.5-4-4.2-6.8-8.2-6.8l-25,0c0.2-0.5,0.5-1.2,0.7-1.8c1.5-3.8,4.3-10.8,4.3-18 c0-8.1-5.7-13-9.6-13.3C57.2,5.5,55.4,7,55,9.7c-0.7,5.1-4.1,12.6-5.5,15.5c-0.4,0.9-0.9,1.7-1.6,2.4c-2.3,2.6-8.1,9-13.6,12.8 c0,0.2,0.1,0.5,0.1,0.7v47.9c0,0.4-0.1,0.8-0.1,1.2c9.4,2.7,17.9,4,27.2,4l21.3,0c3.7,0,7.2-2.5,7.9-6.1c0.6-3-0.5-5.7-2.5-7.5 c3.4-0.8,6-3.9,6-7.5c0-2.3-1-4.4-2.7-5.8c3.4-0.8,6-3.9,6-7.5C97.5,57,96,54.5,93.6,53.1z\" fill=\"${likedColor}\"/>\n <path d=\"M23.4,36.9H6.7c-2.3,0-4.2,1.9-4.2,4.2v47.9c0,2.3,1.9,4.2,4.2,4.2h16.7c2.3,0,4.2-1.9,4.2-4.2V41.2 C27.6,38.8,25.8,36.9,23.4,36.9z M15.1,85.9c-2.4,0-4.4-2-4.4-4.4s2-4.4,4.4-4.4c2.4,0,4.4,2,4.4,4.4S17.5,85.9,15.1,85.9z\" fill=\"${likedColor}\"/>\n </svg>`;\n } else {\n // Outline thumbs up\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 100 100\" width=\"24\" height=\"24\">\n <path d=\"M93.6,53.1c2.6-1.5,4.2-4.4,3.8-7.6c-0.5-4-4.2-6.8-8.2-6.8l-25,0c0.2-0.5,0.5-1.2,0.7-1.8c1.5-3.8,4.3-10.8,4.3-18 c0-8.1-5.7-13-9.6-13.3C57.2,5.5,55.4,7,55,9.7c-0.7,5.1-4.1,12.6-5.5,15.5c-0.4,0.9-0.9,1.7-1.6,2.4c-2.3,2.6-8.1,9-13.6,12.8 c0,0.2,0.1,0.5,0.1,0.7v47.9c0,0.4-0.1,0.8-0.1,1.2c9.4,2.7,17.9,4,27.2,4l21.3,0c3.7,0,7.2-2.5,7.9-6.1c0.6-3-0.5-5.7-2.5-7.5 c3.4-0.8,6-3.9,6-7.5c0-2.3-1-4.4-2.7-5.8c3.4-0.8,6-3.9,6-7.5C97.5,57,96,54.5,93.6,53.1z\" fill=\"none\" stroke=\"${outlineColor}\" stroke-width=\"2\"/>\n <path d=\"M23.4,36.9H6.7c-2.3,0-4.2,1.9-4.2,4.2v47.9c0,2.3,1.9,4.2,4.2,4.2h16.7c2.3,0,4.2-1.9,4.2-4.2V41.2 C27.6,38.8,25.8,36.9,23.4,36.9z M15.1,85.9c-2.4,0-4.4-2-4.4-4.4s2-4.4,4.4-4.4c2.4,0,4.4,2,4.4,4.4S17.5,85.9,15.1,85.9z\" fill=\"none\" stroke=\"${outlineColor}\" stroke-width=\"2\"/>\n </svg>`;\n }\n}\n","// SPDX-License-Identifier: MIT\n\nimport { getComponentStyles } from '../common/base-styles';\n\nexport function getLikeButtonStyles(): string {\n const customStyles = `\n /* === LIKE BUTTON CONTAINER PATTERN === */\n :host {\n /* Icon sizing (overridable via CSS variables) */\n --nostrc-icon-height: 25px;\n --nostrc-icon-width: 25px;\n\n /* Like button CSS variables (overridable by parent components) */\n --nostrc-like-btn-padding: var(--nostrc-spacing-sm) var(--nostrc-spacing-md);\n --nostrc-like-btn-border-radius: var(--nostrc-border-radius-md);\n --nostrc-like-btn-border: var(--nostrc-border-width) solid var(--nostrc-color-border);\n --nostrc-like-btn-min-height: 47px;\n --nostrc-like-btn-width: auto;\n --nostrc-like-btn-horizontal-alignment: left;\n --nostrc-like-btn-bg: var(--nostrc-theme-bg, #ffffff);\n --nostrc-like-btn-color: var(--nostrc-theme-text-primary, #333333);\n --nostrc-like-btn-font-family: var(--nostrc-font-family-primary);\n --nostrc-like-btn-font-size: var(--nostrc-font-size-base);\n \n /* Hover state variables */\n --nostrc-like-btn-hover-bg: var(--nostrc-theme-hover-bg, rgba(0, 0, 0, 0.05));\n --nostrc-like-btn-hover-color: var(--nostrc-theme-text-primary, #333333);\n --nostrc-like-btn-hover-border: var(--nostrc-border-width) solid var(--nostrc-theme-border, var(--nostrc-color-border));\n\n /* Liked state variables */\n --nostrc-like-btn-liked-bg: #e7f3ff;\n --nostrc-like-btn-liked-color: #1877f2;\n --nostrc-like-btn-liked-border: #1877f2;\n --nostrc-like-btn-liked-hover-bg: #d1e7ff;\n\n /* Make the host a flex container for button + count */\n display: inline-flex;\n flex-direction: row;\n align-items: center;\n gap: var(--nostrc-spacing-md);\n font-family: var(--nostrc-like-btn-font-family);\n font-size: var(--nostrc-like-btn-font-size);\n }\n\n /* Focus state for accessibility */\n :host(:focus-visible) {\n outline: 2px solid var(--nostrc-color-primary, #007bff);\n outline-offset: 2px;\n }\n\n :host(.is-error) .nostr-like-button-container {\n border: var(--nostrc-border-width) solid var(--nostrc-color-error-text);\n border-radius: var(--nostrc-border-radius-md);\n padding: var(--nostrc-spacing-sm);\n color: var(--nostrc-color-error-text);\n }\n\n .nostr-like-button-container {\n display: flex;\n align-items: center;\n gap: var(--nostrc-spacing-md);\n width: fit-content;\n }\n\n .nostr-like-button-left-container {\n display: flex;\n align-items: center;\n }\n\n .nostr-like-button-right-container {\n display: flex;\n align-items: center;\n }\n\n .nostr-like-button {\n display: flex;\n align-items: center;\n justify-content: var(--nostrc-like-btn-horizontal-alignment);\n gap: var(--nostrc-spacing-sm);\n background: var(--nostrc-like-btn-bg);\n color: var(--nostrc-like-btn-color);\n border: var(--nostrc-like-btn-border);\n border-radius: var(--nostrc-like-btn-border-radius);\n padding: var(--nostrc-like-btn-padding);\n min-height: var(--nostrc-like-btn-min-height);\n width: var(--nostrc-like-btn-width);\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;\n font-family: inherit;\n font-size: inherit;\n }\n\n /* Hover state on the button */\n .nostr-like-button:hover {\n background: var(--nostrc-like-btn-hover-bg);\n color: var(--nostrc-like-btn-hover-color);\n border: var(--nostrc-like-btn-hover-border);\n }\n\n /* Liked state */\n .nostr-like-button.liked {\n background: var(--nostrc-like-btn-liked-bg);\n color: var(--nostrc-like-btn-liked-color);\n border: var(--nostrc-border-width) solid var(--nostrc-like-btn-liked-border);\n }\n\n .nostr-like-button.liked:hover {\n background: var(--nostrc-like-btn-liked-hover-bg);\n }\n\n .nostr-like-button:disabled {\n pointer-events: none;\n user-select: none;\n opacity: 0.6;\n }\n\n :host:not([status=\"ready\"]) .nostr-like-button {\n cursor: not-allowed;\n }\n\n /* SVG Icon Styles */\n .nostr-like-button svg {\n display: inline-block;\n vertical-align: middle;\n width: var(--nostrc-icon-width);\n height: var(--nostrc-icon-height);\n }\n\n /* Like count display */\n .like-count {\n font-size: var(--nostrc-font-size-sm);\n color: var(--nostrc-theme-text-secondary, #666666);\n white-space: nowrap;\n text-decoration: underline;\n text-decoration-color: transparent;\n transition: text-decoration-color 0.2s ease, color 0.2s ease;\n }\n\n /* Clickable like count */\n .like-count.clickable {\n cursor: pointer;\n text-decoration-color: currentColor;\n }\n\n .like-count.clickable:hover {\n color: var(--nostrc-color-primary, #7f00ff);\n text-decoration-color: var(--nostrc-color-primary, #7f00ff);\n }\n\n /* Help icon */\n .help-icon {\n background: none;\n border: 1px solid var(--nostrc-color-border, #e0e0e0);\n border-radius: var(--nostrc-border-radius-full, 50%);\n width: var(--nostrc-help-icon-size, 16px);\n height: var(--nostrc-help-icon-size, 16px);\n font-size: calc(var(--nostrc-help-icon-size, 16px) * 0.7);\n font-weight: bold;\n color: var(--nostrc-theme-text-secondary, #666666);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n margin-left: var(--nostrc-spacing-xs, 4px);\n transition: all 0.2s ease;\n }\n\n .help-icon:hover {\n background: var(--nostrc-color-hover-background, rgba(0, 0, 0, 0.05));\n border-color: var(--nostrc-color-primary, #7f00ff);\n color: var(--nostrc-color-primary, #7f00ff);\n }\n\n /* Skeleton loader for like count */\n .like-count.skeleton {\n background: linear-gradient(90deg, \n var(--nostrc-skeleton-color-min) 25%, \n var(--nostrc-skeleton-color-max) 50%, \n var(--nostrc-skeleton-color-min) 75%\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 width: 80px;\n height: 1.2em;\n display: inline-block;\n }\n\n /* Skeleton loader for button text */\n .button-text-skeleton {\n background: linear-gradient(90deg, \n var(--nostrc-skeleton-color-min) 25%, \n var(--nostrc-skeleton-color-max) 50%, \n var(--nostrc-skeleton-color-min) 75%\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 width: 60px;\n height: 1em;\n display: inline-block;\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 /* Error message styling */\n .nostr-like-button-error small {\n color: var(--nostrc-color-error-text);\n font-size: var(--nostrc-font-size-sm);\n line-height: 1em;\n max-width: 250px;\n white-space: pre-line;\n }\n `;\n \n return getComponentStyles(customStyles);\n}\n","// SPDX-License-Identifier: MIT\n\nexport function getHelpDialogStyles(): string {\n return `\n .help-content {\n padding: var(--nostrc-spacing-md, 12px);\n }\n\n .help-content p {\n margin: 0 0 var(--nostrc-spacing-md, 12px) 0;\n color: var(--nostrc-theme-text-primary, #333333);\n line-height: 1.5;\n }\n\n .help-content p:last-child {\n margin-bottom: 0;\n }\n\n .help-content ul {\n margin: 0 0 var(--nostrc-spacing-md, 12px) 0;\n padding-left: var(--nostrc-spacing-lg, 16px);\n color: var(--nostrc-theme-text-primary, #333333);\n }\n\n .help-content li {\n margin-bottom: var(--nostrc-spacing-xs, 4px);\n line-height: 1.5;\n }\n\n .help-content li:last-child {\n margin-bottom: 0;\n }\n\n .help-content strong {\n font-weight: 600;\n color: var(--nostrc-theme-text-primary, #333333);\n }\n\n .help-content a {\n color: var(--nostrc-theme-primary, #0066cc);\n text-decoration: underline;\n }\n\n .help-content a:hover {\n color: var(--nostrc-theme-primary-hover, #0052a3);\n }\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 { getHelpDialogStyles } from './dialog-help-style';\n\nexport const injectHelpDialogStyles = (): void => {\n // Check if styles are already injected\n if (document.querySelector('style[data-help-dialog-styles]')) return;\n \n const style = document.createElement('style');\n style.setAttribute('data-help-dialog-styles', 'true');\n style.textContent = getHelpDialogStyles();\n document.head.appendChild(style);\n};\n\nexport const showHelpDialog = async (theme?: 'light' | 'dark'): Promise<void> => {\n injectHelpDialogStyles();\n \n if (!customElements.get('dialog-component')) {\n await customElements.whenDefined('dialog-component');\n }\n \n const dialogComponent = document.createElement('dialog-component') as DialogComponent;\n dialogComponent.setAttribute('header', 'What is a Like?');\n if (theme) {\n dialogComponent.setAttribute('data-theme', theme);\n }\n \n // Set dialog content\n dialogComponent.innerHTML = `\n <div class=\"help-content\">\n <p>Like any webpage to show your appreciation! Your likes are stored on Nostr, a decentralized network you control—no accounts needed.</p>\n <ul>\n <li>Like any webpage or article</li>\n <li>See who liked the content</li>\n <li>Works with a browser extension like <a href=\"https://getalby.com\" target=\"_blank\" rel=\"noopener noreferrer\">Alby</a> or nos2x</li>\n </ul>\n </div>\n `;\n \n dialogComponent.showModal();\n};\n\n","// utils.ts\nvar utf8Decoder = new TextDecoder(\"utf-8\");\nvar utf8Encoder = new TextEncoder();\nfunction normalizeURL(url) {\n if (url.indexOf(\"://\") === -1)\n url = \"wss://\" + url;\n let p = new URL(url);\n p.pathname = p.pathname.replace(/\\/+/g, \"/\");\n if (p.pathname.endsWith(\"/\"))\n p.pathname = p.pathname.slice(0, -1);\n if (p.port === \"80\" && p.protocol === \"ws:\" || p.port === \"443\" && p.protocol === \"wss:\")\n p.port = \"\";\n p.searchParams.sort();\n p.hash = \"\";\n return p.toString();\n}\nfunction insertEventIntoDescendingList(sortedArray, event) {\n const [idx, found] = binarySearch(sortedArray, (b) => {\n if (event.id === b.id)\n return 0;\n if (event.created_at === b.created_at)\n return -1;\n return b.created_at - event.created_at;\n });\n if (!found) {\n sortedArray.splice(idx, 0, event);\n }\n return sortedArray;\n}\nfunction insertEventIntoAscendingList(sortedArray, event) {\n const [idx, found] = binarySearch(sortedArray, (b) => {\n if (event.id === b.id)\n return 0;\n if (event.created_at === b.created_at)\n return -1;\n return event.created_at - b.created_at;\n });\n if (!found) {\n sortedArray.splice(idx, 0, event);\n }\n return sortedArray;\n}\nfunction binarySearch(arr, compare) {\n let start = 0;\n let end = arr.length - 1;\n while (start <= end) {\n const mid = Math.floor((start + end) / 2);\n const cmp = compare(arr[mid]);\n if (cmp === 0) {\n return [mid, true];\n }\n if (cmp < 0) {\n end = mid - 1;\n } else {\n start = mid + 1;\n }\n }\n return [start, false];\n}\nvar QueueNode = class {\n value;\n next = null;\n prev = null;\n constructor(message) {\n this.value = message;\n }\n};\nvar Queue = class {\n first;\n last;\n constructor() {\n this.first = null;\n this.last = null;\n }\n enqueue(value) {\n const newNode = new QueueNode(value);\n if (!this.last) {\n this.first = newNode;\n this.last = newNode;\n } else if (this.last === this.first) {\n this.last = newNode;\n this.last.prev = this.first;\n this.first.next = newNode;\n } else {\n newNode.prev = this.last;\n this.last.next = newNode;\n this.last = newNode;\n }\n return true;\n }\n dequeue() {\n if (!this.first)\n return null;\n if (this.first === this.last) {\n const target2 = this.first;\n this.first = null;\n this.last = null;\n return target2.value;\n }\n const target = this.first;\n this.first = target.next;\n return target.value;\n }\n};\nexport {\n Queue,\n QueueNode,\n binarySearch,\n insertEventIntoAscendingList,\n insertEventIntoDescendingList,\n normalizeURL,\n utf8Decoder,\n utf8Encoder\n};\n","// SPDX-License-Identifier: MIT\n\nimport { SimplePool } from 'nostr-tools';\nimport { normalizeURL } from 'nostr-tools/utils';\n\n/**\n * Helper utilities for Nostr like operations using NIP-25 External Content Reactions.\n * These are deliberately kept self-contained so `nostr-like` Web Component can import\n * everything from a single module without polluting the rest of the codebase.\n */\n\nexport interface LikeDetails {\n authorPubkey: string;\n date: Date;\n content: string;\n}\n\nexport interface LikeCountResult {\n totalCount: number;\n likeDetails: LikeDetails[];\n likedCount: number;\n dislikedCount: number;\n}\n\n/**\n * Fetch all likes for a URL using NIP-25 kind 17 events\n */\nexport async function fetchLikesForUrl(\n url: string, \n relays: string[]\n): Promise<LikeCountResult> {\n // Normalize URL at the beginning for consistent comparison with tags\n const normalizedUrl = normalizeURL(url);\n \n const pool = new SimplePool();\n \n try {\n // Query kind 17 events (both likes and unlikes)\n const events = await pool.querySync(relays, {\n kinds: [17],\n '#k': ['web'],\n '#i': [normalizedUrl],\n limit: 1000\n });\n \n const likes: LikeDetails[] = [];\n let likedCount = 0;\n let dislikedCount = 0;\n \n for (const event of events) {\n likes.push({\n authorPubkey: event.pubkey,\n date: new Date(event.created_at * 1000),\n content: event.content\n });\n \n if (event.content === '-') {\n dislikedCount++;\n } else {\n likedCount++;\n }\n }\n \n likes.sort((a, b) => b.date.getTime() - a.date.getTime());\n \n const totalCount = likedCount - dislikedCount;\n \n return {\n totalCount: totalCount,\n likeDetails: likes,\n likedCount: likedCount,\n dislikedCount: dislikedCount\n };\n } catch (error) {\n // Rethrow error so callers can handle relay/network failures appropriately\n throw error instanceof Error ? error : new Error(String(error));\n } finally {\n pool.close(relays);\n }\n}\n\n/**\n * Create reaction event (kind 17)\n * @param url - URL to react to\n * @param content - '+' for like, '-' for unlike\n */\nexport function createReactionEvent(url: string, content: '+' | '-'): any {\n return {\n kind: 17,\n content,\n tags: [\n ['k', 'web'],\n ['i', url]\n ],\n created_at: Math.floor(Date.now() / 1000)\n };\n}\n\n/**\n * Create like event (kind 17)\n * @deprecated Use createReactionEvent(url, '+') instead\n */\nexport function createLikeEvent(url: string): any {\n return createReactionEvent(url, '+');\n}\n\n/**\n * Create unlike event (kind 17 with '-' content)\n * @deprecated Use createReactionEvent(url, '-') instead\n */\nexport function createUnlikeEvent(url: string): any {\n return createReactionEvent(url, '-');\n}\n\n/**\n * Check if user has liked a URL\n */\nexport async function hasUserLiked(\n url: string,\n userPubkey: string,\n relays: string[]\n): Promise<boolean> {\n const pool = new SimplePool();\n const normalizedUrl = url;\n \n try {\n // Get user's latest reaction for this URL\n const events = await pool.querySync(relays, {\n kinds: [17],\n authors: [userPubkey],\n '#k': ['web'],\n '#i': [normalizedUrl],\n limit: 1\n });\n \n if (events.length === 0) return false;\n \n // Check if latest reaction is a like (not an unlike)\n const latest = events[0];\n return latest.content === '+' || latest.content === '';\n } catch (error) {\n console.error(\"Nostr-Components: Like button: Error checking user like status\", error);\n return false;\n } finally {\n pool.close(relays);\n }\n}\n\n/**\n * Get user's pubkey from NIP-07 signer\n */\nexport async function getUserPubkey(): Promise<string | null> {\n try {\n if (typeof window !== 'undefined' && (window as any).nostr) {\n const nip07signer = (window as any).nostr;\n const user = await nip07signer.getPublicKey();\n return user;\n }\n } catch (error) {\n console.error(\"Nostr-Components: Like button: Error getting user pubkey\", error);\n }\n return null;\n}\n\n/**\n * Sign event with NIP-07\n */\nexport async function signEvent(event: any): Promise<any> {\n try {\n if (typeof window !== 'undefined' && (window as any).nostr) {\n const nip07signer = (window as any).nostr;\n const signedEvent = await nip07signer.signEvent(event);\n return signedEvent;\n }\n throw new Error('NIP-07 extension not available');\n } catch (error) {\n console.error(\"Nostr-Components: Like button: Error signing event\", error);\n throw error;\n }\n}\n\n/**\n * Check if NIP-07 extension is available\n */\nexport function isNip07Available(): boolean {\n return typeof window !== 'undefined' && !!(window as any).nostr;\n}\n","// SPDX-License-Identifier: MIT\n\nimport { NostrBaseComponent } from '../base/base-component/nostr-base-component';\nimport { NCStatus } from '../base/base-component/nostr-base-component';\nimport { NDKEvent } from '@nostr-dev-kit/ndk';\nimport { renderLikeButton, RenderLikeButtonOptions } from './render';\nimport { getLikeButtonStyles } from './style';\nimport { showHelpDialog } from './dialog-help';\nimport { isValidUrl } from '../common/utils';\nimport { \n fetchLikesForUrl, \n createLikeEvent,\n createUnlikeEvent,\n hasUserLiked, \n getUserPubkey, \n signEvent, \n isNip07Available,\n LikeCountResult \n} from './like-utils';\nimport { normalizeURL } from 'nostr-tools/utils';\n\n/**\n * <nostr-like>\n * Attributes:\n * - url (optional) : URL to like (default: current page URL)\n * - text (optional) : custom text (default \"Like\") (Max 32 characters)\n * - relays (optional) : comma-separated relay URLs\n * - data-theme (optional) : \"light\" | \"dark\" (default light)\n * \n * Features:\n * - URL-based likes using NIP-25 kind 17 events\n * - Click count to view likers\n */\nexport default class NostrLike extends NostrBaseComponent {\n protected likeActionStatus = this.channel('likeAction');\n protected likeListStatus = this.channel('likeList');\n \n private currentUrl: string = '';\n private isLiked: boolean = false;\n private likeCount: number = 0;\n private cachedLikeDetails: LikeCountResult | null = null;\n private loadSeq = 0;\n\n constructor() {\n super();\n }\n\n connectedCallback() {\n super.connectedCallback?.();\n if (this.likeListStatus.get() === NCStatus.Idle) {\n this.initChannelStatus('likeList', NCStatus.Loading, { reflectOverall: false });\n }\n this.attachDelegatedListeners();\n this.render();\n }\n\n static get observedAttributes() {\n return [\n ...super.observedAttributes,\n 'url',\n 'text'\n ];\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null\n ) {\n if (oldValue === newValue) return;\n super.attributeChangedCallback(name, oldValue, newValue);\n \n if (name === 'url' || name === 'text') {\n this.likeActionStatus.set(NCStatus.Ready);\n this.likeListStatus.set(NCStatus.Loading);\n this.isLiked = false;\n this.errorMessage = '';\n this.updateLikeCount();\n this.render();\n }\n }\n\n /** Base class functions */\n protected validateInputs(): boolean {\n if (!super.validateInputs()) {\n this.likeActionStatus.set(NCStatus.Idle);\n this.likeListStatus.set(NCStatus.Idle);\n return false;\n }\n\n const urlAttr = this.getAttribute('url');\n const textAttr = this.getAttribute('text');\n const tagName = this.tagName.toLowerCase();\n\n let errorMessage: string | null = null;\n\n if (urlAttr) {\n if (!isValidUrl(urlAttr)) {\n errorMessage = 'Invalid URL format';\n }\n }\n\n if (textAttr && textAttr.length > 32) {\n errorMessage = 'Max text length: 32 characters';\n }\n\n if (errorMessage) {\n this.likeActionStatus.set(NCStatus.Error, errorMessage);\n this.likeListStatus.set(NCStatus.Error, errorMessage);\n console.error(`Nostr-Components: ${tagName}: ${errorMessage}`);\n return false;\n }\n\n return true;\n }\n\n protected onStatusChange(_status: NCStatus) {\n this.render();\n }\n\n protected onNostrRelaysConnected() {\n this.updateLikeCount();\n this.render();\n }\n\n /** Private functions */\n /**\n * Lazy initializer for currentUrl - ensures it's set before like/unlike operations\n */\n private ensureCurrentUrl(): void {\n if (!this.currentUrl) {\n this.currentUrl = normalizeURL(this.getAttribute('url') || window.location.href);\n }\n }\n\n private async updateLikeCount() {\n const seq = ++this.loadSeq;\n try {\n await this.ensureNostrConnected();\n this.currentUrl = normalizeURL(this.getAttribute('url') || window.location.href);\n this.likeListStatus.set(NCStatus.Loading);\n this.render();\n \n const result = await fetchLikesForUrl(this.currentUrl, this.getRelays());\n if (seq !== this.loadSeq) return; // stale\n this.likeCount = result.totalCount;\n this.cachedLikeDetails = result;\n this.likeListStatus.set(NCStatus.Ready);\n } catch (error) {\n console.error('[NostrLike] Failed to fetch like count:', error);\n this.likeListStatus.set(NCStatus.Error, 'Failed to load likes');\n } finally {\n this.render();\n }\n }\n\n // TODO: Do onboarding logic here\n private async handleLikeClick() {\n // Ensure currentUrl is set before proceeding\n this.ensureCurrentUrl();\n \n if (!this.currentUrl) {\n this.likeActionStatus.set(NCStatus.Error, 'Invalid URL');\n this.render();\n return;\n }\n\n this.likeActionStatus.set(NCStatus.Loading);\n if (!isNip07Available()) {\n this.likeActionStatus.set(NCStatus.Error, \n 'Please install a Nostr browser extension (Alby, nos2x, etc.)'\n );\n this.render();\n return;\n }\n\n // Check user like status\n try {\n const userPubkey = await getUserPubkey();\n if (userPubkey) {\n this.isLiked = await hasUserLiked(this.currentUrl, userPubkey, this.getRelays());\n }\n } catch (error) {\n console.error('[NostrLike] Failed to check user like status:', error);\n this.likeActionStatus.set(NCStatus.Error, 'Failed to check user like status');\n } finally {\n this.render();\n }\n\n // If already liked, show confirmation dialog\n if (this.isLiked) {\n const confirmed = window.confirm('You have already liked this. Do you want to unlike it?');\n if (!confirmed) {\n this.likeActionStatus.set(NCStatus.Ready);\n this.render();\n return;\n }\n \n // Proceed with unlike\n await this.handleUnlike();\n } else {\n // Proceed with like\n await this.handleLike();\n }\n }\n\n private async handleLike() {\n // Ensure currentUrl is set before proceeding\n this.ensureCurrentUrl();\n \n if (!this.currentUrl) {\n this.likeActionStatus.set(NCStatus.Error, 'Invalid URL');\n this.render();\n return;\n }\n\n this.likeActionStatus.set(NCStatus.Loading);\n this.render();\n\n try {\n // Create like event\n const event = createLikeEvent(this.currentUrl);\n \n // Sign with NIP-07\n const signedEvent = await signEvent(event);\n \n // Create NDKEvent and publish\n const ndkEvent = new NDKEvent(this.nostrService.getNDK(), signedEvent);\n await ndkEvent.publish();\n \n // Update state optimistically\n this.isLiked = true;\n this.likeCount++;\n this.likeActionStatus.set(NCStatus.Ready);\n \n // Refresh like count to get accurate data\n await this.updateLikeCount();\n } catch (error) {\n console.error('[NostrLike] Failed to like:', error);\n \n // Rollback optimistic update\n this.isLiked = false;\n this.likeCount--;\n \n const errorMessage = error instanceof Error ? error.message : 'Failed to like';\n this.likeActionStatus.set(NCStatus.Error, errorMessage);\n } finally {\n this.render();\n }\n }\n\n private async handleUnlike() {\n // Ensure currentUrl is set before proceeding\n this.ensureCurrentUrl();\n \n if (!this.currentUrl) {\n this.likeActionStatus.set(NCStatus.Error, 'Invalid URL');\n this.render();\n return;\n }\n\n this.likeActionStatus.set(NCStatus.Loading);\n this.render();\n \n try {\n // Create unlike event\n const event = createUnlikeEvent(this.currentUrl);\n \n // Sign with NIP-07\n const signedEvent = await signEvent(event);\n \n // Create NDKEvent and publish\n const ndkEvent = new NDKEvent(this.nostrService.getNDK(), signedEvent);\n await ndkEvent.publish();\n \n // Update state optimistically\n this.isLiked = false;\n if (this.likeCount > 0) {\n this.likeCount--;\n }\n this.likeActionStatus.set(NCStatus.Ready);\n \n // Refresh like count to get accurate data\n await this.updateLikeCount();\n } catch (error) {\n console.error('[NostrLike] Failed to unlike:', error);\n \n // Rollback optimistic update\n this.isLiked = true;\n this.likeCount++;\n \n const errorMessage = error instanceof Error ? error.message : 'Failed to unlike';\n this.likeActionStatus.set(NCStatus.Error, errorMessage);\n } finally {\n this.render();\n }\n }\n\n private async handleCountClick() {\n if (this.likeCount === 0 || !this.cachedLikeDetails) {\n return;\n }\n\n try {\n // Import dialog dynamically to avoid circular dependencies\n const { openLikersDialog } = await import('./dialog-likers');\n await openLikersDialog({\n likeDetails: this.cachedLikeDetails.likeDetails,\n theme: this.theme === 'dark' ? 'dark' : 'light',\n });\n } catch (error) {\n console.error('[NostrLike] Error opening likers dialog:', error);\n }\n }\n\n private async handleHelpClick() {\n try {\n await showHelpDialog(this.theme === 'dark' ? 'dark' : 'light');\n } catch (error) {\n console.error('[NostrLike] Error showing help dialog:', error);\n }\n }\n\n private attachDelegatedListeners() {\n this.delegateEvent('click', '.nostr-like-button', (e) => {\n e.preventDefault?.();\n e.stopPropagation?.();\n void this.handleLikeClick();\n });\n\n this.delegateEvent('click', '.like-count', (e) => {\n e.preventDefault?.();\n e.stopPropagation?.();\n void this.handleCountClick();\n });\n\n this.delegateEvent('click', '.help-icon', (e) => {\n e.preventDefault?.();\n e.stopPropagation?.();\n this.handleHelpClick();\n });\n }\n\n protected renderContent() {\n // console.log(`Like: Render: conn: ${this.conn.get()}, likeActionStatus: ${this.likeActionStatus.get()}, likeListStatus: ${this.likeListStatus.get()}`);\n const isLoading = this.likeActionStatus.get() === NCStatus.Loading || this.conn.get() === NCStatus.Loading;\n const isCountLoading = this.likeListStatus.get() === NCStatus.Loading;\n const isError = this.computeOverall() === NCStatus.Error;\n const errorMessage = this.errorMessage;\n const buttonText = this.getAttribute('text') || 'Like';\n\n const renderOptions: RenderLikeButtonOptions = {\n isLoading,\n isError,\n errorMessage,\n buttonText,\n isLiked: this.isLiked,\n likeCount: this.likeCount,\n hasLikes: this.likeCount > 0,\n isCountLoading,\n theme: this.theme as 'light' | 'dark',\n };\n\n this.shadowRoot!.innerHTML = `\n ${getLikeButtonStyles()}\n ${renderLikeButton(renderOptions)}\n `;\n }\n}\n\ncustomElements.define('nostr-like', NostrLike);\n"],"file":"components/nostr-like.es.js"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{m as v,e as s,g as m,a as d,p as b}from"../assets/base-styles-CBypR3FR.js";import{N as y}from"../assets/nostr-user-component-
|
|
1
|
+
import{m as v,e as s,g as m,a as d,p as b}from"../assets/base-styles-CBypR3FR.js";import{N as y}from"../assets/nostr-user-component-BOdux8_6.js";import{h as w}from"../assets/nostr-service-pr_crY62.js";import{r as x,a as C,b as E,c as N}from"../assets/copy-delegation-C4uvRTVM.js";import"../assets/user-resolver-C-E6KdwY.js";function A({isLoading:n,isError:r,errorMessage:o,userProfile:e,ndkUser:t,showNpub:a,showFollow:c}){if(n)return L();if(r||e==null)return S(o||"");const i=e.displayName||e.name||v((t==null?void 0:t.npub)||""),p=s(i),f=s(e.picture||w),g=(t==null?void 0:t.npub)||"",h=(e==null?void 0:e.nip05)||"",u=s((t==null?void 0:t.pubkey)||"");return l(`<img src='${f}' alt='Nostr profile image of ${p}' loading="lazy" decoding="async"/>`,`${x({name:i})}
|
|
2
2
|
${e.nip05?C(h):""}
|
|
3
3
|
${a===!0?E(g||""):""}
|
|
4
4
|
${c===!0&&(t!=null&&t.pubkey)?`<nostr-follow-button pubkey="${u}"></nostr-follow-button>`:""}`)}function L(){return l('<div class="skeleton img-skeleton"></div>',`<div class="skeleton" style="width: 120px;"></div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var S=Object.defineProperty;var L=(e,o,t)=>o in e?S(e,o,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[o]=t;var n=(e,o,t)=>L(e,typeof o!="symbol"?o+"":o,t);import{e as k,g as $,a as p}from"../assets/base-styles-CBypR3FR.js";import{N as z}from"../assets/nostr-user-component-
|
|
1
|
+
var S=Object.defineProperty;var L=(e,o,t)=>o in e?S(e,o,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[o]=t;var n=(e,o,t)=>L(e,typeof o!="symbol"?o+"":o,t);import{e as k,g as $,a as p}from"../assets/base-styles-CBypR3FR.js";import{N as z}from"../assets/nostr-user-component-BOdux8_6.js";import{r as F,a as C,b as E,d as N,c as A}from"../assets/copy-delegation-C4uvRTVM.js";import"../assets/nostr-service-pr_crY62.js";import"../assets/user-resolver-C-E6KdwY.js";function l(e,o,t){const s=k(e);return`
|
|
2
2
|
<div class="stat" data-orientation="horizontal" aria-busy="${t}" aria-live="polite">
|
|
3
3
|
<div class="stat-inner">
|
|
4
4
|
<div class="stat-value">
|