diff --git a/app/web/favicon.ico b/app/web/favicon.ico new file mode 100644 index 0000000..4823711 Binary files /dev/null and b/app/web/favicon.ico differ diff --git a/app/web/public/build/bundle.css b/app/web/public/build/bundle.css new file mode 100644 index 0000000..ed3dcdf --- /dev/null +++ b/app/web/public/build/bundle.css @@ -0,0 +1,2 @@ +.modal-overlay.svelte-9yzcwg.svelte-9yzcwg{position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0, 0, 0, 0.5);display:flex;justify-content:center;align-items:center;z-index:1000}.modal.svelte-9yzcwg.svelte-9yzcwg{background:var(--bg-color);border-radius:8px;box-shadow:0 4px 20px rgba(0, 0, 0, 0.3);width:90%;max-width:500px;max-height:90vh;overflow-y:auto;border:1px solid var(--border-color)}.modal-header.svelte-9yzcwg.svelte-9yzcwg{display:flex;justify-content:space-between;align-items:center;padding:20px;border-bottom:1px solid var(--border-color)}.modal-header.svelte-9yzcwg h2.svelte-9yzcwg{margin:0;color:var(--text-color);font-size:1.5rem}.close-btn.svelte-9yzcwg.svelte-9yzcwg{background:none;border:none;font-size:1.5rem;cursor:pointer;color:var(--text-color);padding:0;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:background-color 0.2s}.close-btn.svelte-9yzcwg.svelte-9yzcwg:hover{background-color:var(--tab-hover-bg)}.tab-container.svelte-9yzcwg.svelte-9yzcwg{padding:20px}.tabs.svelte-9yzcwg.svelte-9yzcwg{display:flex;border-bottom:1px solid var(--border-color);margin-bottom:20px}.tab-btn.svelte-9yzcwg.svelte-9yzcwg{flex:1;padding:12px 16px;background:none;border:none;cursor:pointer;color:var(--text-color);font-size:1rem;transition:all 0.2s;border-bottom:2px solid transparent}.tab-btn.svelte-9yzcwg.svelte-9yzcwg:hover{background-color:var(--tab-hover-bg)}.tab-btn.active.svelte-9yzcwg.svelte-9yzcwg{border-bottom-color:var(--primary);color:var(--primary)}.tab-content.svelte-9yzcwg.svelte-9yzcwg{min-height:200px}.extension-login.svelte-9yzcwg.svelte-9yzcwg,.nsec-login.svelte-9yzcwg.svelte-9yzcwg{display:flex;flex-direction:column;gap:16px}.extension-login.svelte-9yzcwg p.svelte-9yzcwg,.nsec-login.svelte-9yzcwg p.svelte-9yzcwg{margin:0;color:var(--text-color);line-height:1.5}.login-extension-btn.svelte-9yzcwg.svelte-9yzcwg,.login-nsec-btn.svelte-9yzcwg.svelte-9yzcwg{padding:12px 24px;background:var(--primary);color:white;border:none;border-radius:6px;cursor:pointer;font-size:1rem;transition:background-color 0.2s}.login-extension-btn.svelte-9yzcwg.svelte-9yzcwg:hover:not(:disabled),.login-nsec-btn.svelte-9yzcwg.svelte-9yzcwg:hover:not(:disabled){background:#00ACC1}.login-extension-btn.svelte-9yzcwg.svelte-9yzcwg:disabled,.login-nsec-btn.svelte-9yzcwg.svelte-9yzcwg:disabled{background:#ccc;cursor:not-allowed}.nsec-input.svelte-9yzcwg.svelte-9yzcwg{padding:12px;border:1px solid var(--input-border);border-radius:6px;font-size:1rem;background:var(--bg-color);color:var(--text-color)}.nsec-input.svelte-9yzcwg.svelte-9yzcwg:focus{outline:none;border-color:var(--primary)}.message.svelte-9yzcwg.svelte-9yzcwg{padding:10px;border-radius:4px;margin-top:16px;text-align:center}.error-message.svelte-9yzcwg.svelte-9yzcwg{background:#ffebee;color:#c62828;border:1px solid #ffcdd2}.success-message.svelte-9yzcwg.svelte-9yzcwg{background:#e8f5e8;color:#2e7d32;border:1px solid #c8e6c9}.modal.dark-theme.svelte-9yzcwg .error-message.svelte-9yzcwg{background:#4a2c2a;color:#ffcdd2;border:1px solid #6d4c41}.modal.dark-theme.svelte-9yzcwg .success-message.svelte-9yzcwg{background:#2e4a2e;color:#a5d6a7;border:1px solid #4caf50} +body{margin:0;padding:0;--bg-color:#ddd;--header-bg:#eee;--border-color:#dee2e6;--text-color:#444444;--sidebar-bg:#eeeeee;--tab-hover-bg:#ddd;--input-border:#ccc;--button-bg:#ddd;--button-hover-bg:#eee;--primary:#00BCD4;--warning:#ff3e00}body.dark-theme{--bg-color:#263238;--header-bg:#1e272c;--border-color:#404040;--text-color:#ffffff;--sidebar-bg:#1e272c;--tab-hover-bg:#263238;--input-border:#555;--button-bg:#263238;--button-hover-bg:#1e272c;--primary:#00BCD4;--warning:#ff3e00}.main-header.svelte-m62qks.svelte-m62qks{height:3em;background-color:var(--header-bg);position:fixed;top:0;left:0;right:0;z-index:1000;color:var(--text-color)}.header-content.svelte-m62qks.svelte-m62qks{height:100%;display:flex;align-items:center;padding:0;gap:0}.logo.svelte-m62qks.svelte-m62qks{height:2.5em;width:2.5em;object-fit:contain;flex-shrink:0}.tab-label-area.svelte-m62qks.svelte-m62qks{flex:1;height:100%;display:flex;align-items:center;gap:0;padding:0 1rem}.selected-tab-label.svelte-m62qks.svelte-m62qks{font-size:1em;font-weight:600;text-transform:capitalize;color:var(--text-color)}.theme-toggle-btn.svelte-m62qks.svelte-m62qks{border:0 none;border-radius:0;display:flex;align-items:center;background-color:var(--button-hover-bg);cursor:pointer;color:var(--text-color);height:3em;width:auto;min-width:3em;flex-shrink:0;line-height:1;transition:background-color 0.2s;justify-content:center;padding:1em 1em 1em 1em;margin:0}.theme-toggle-btn.svelte-m62qks.svelte-m62qks:hover{background-color:var(--button-bg)}.login-btn.svelte-m62qks.svelte-m62qks{border:0 none;border-radius:0;display:flex;align-items:center;background-color:var(--primary);cursor:pointer;color:var(--text-color);height:3em;width:auto;min-width:3em;flex-shrink:0;line-height:1;transition:background-color 0.2s;justify-content:center;padding:1em 1em 1em 1em;margin:0}.login-btn.svelte-m62qks.svelte-m62qks:hover{background-color:#0056b3}.app-container.svelte-m62qks.svelte-m62qks{display:flex;margin-top:3em;height:calc(100vh - 3em)}.sidebar.svelte-m62qks.svelte-m62qks{position:fixed;left:0;top:3em;bottom:0;width:140px;background-color:var(--sidebar-bg);transition:width 0.3s ease;overflow:hidden;color:var(--text-color);z-index:100}.sidebar.collapsed.svelte-m62qks.svelte-m62qks{width:60px}.sidebar-content.svelte-m62qks.svelte-m62qks{height:100%;display:flex;flex-direction:column;padding:0}.tabs.svelte-m62qks.svelte-m62qks{flex:1}.tab.svelte-m62qks.svelte-m62qks{height:2.5em;display:flex;align-items:center;padding:0 1rem;cursor:pointer;transition:background-color 0.2s ease;margin-bottom:0.5em;gap:0.75rem;border:none;background:none;width:100%;text-align:left}.tab.svelte-m62qks.svelte-m62qks:hover{background-color:var(--tab-hover-bg)}.tab.active.svelte-m62qks.svelte-m62qks{background-color:var(--tab-hover-bg);border-right:3px solid var(--primary)}.tab-icon.svelte-m62qks.svelte-m62qks{font-size:1.2em;flex-shrink:0;width:1.5em;text-align:center}.tab-label.svelte-m62qks.svelte-m62qks{font-size:0.9em;font-weight:500;white-space:nowrap}.toggle-btn.svelte-m62qks.svelte-m62qks{height:2.5em;margin:0;padding:0.5em 1em;background-color:transparent;cursor:pointer;transition:background-color 0.2s ease;color:var(--text-color);border:none}.toggle-btn.svelte-m62qks.svelte-m62qks:hover{background-color:var(--tab-hover-bg)}.main-content.svelte-m62qks.svelte-m62qks{position:fixed;left:140px;top:3em;right:0;bottom:0;padding:2rem;overflow-y:auto;background-color:var(--bg-color);color:var(--text-color);transition:left 0.3s ease}.app-container.svelte-m62qks:has(.sidebar.collapsed) .main-content.svelte-m62qks{left:60px}.main-content.svelte-m62qks h1.svelte-m62qks{color:#ff3e00;text-transform:uppercase;font-size:4em;font-weight:100;text-align:center}@media(max-width: 640px){.header-content.svelte-m62qks.svelte-m62qks{padding:0}.sidebar.svelte-m62qks.svelte-m62qks{width:120px}.sidebar.collapsed.svelte-m62qks.svelte-m62qks{width:50px}.main-content.svelte-m62qks.svelte-m62qks{left:120px;padding:1rem}.app-container.svelte-m62qks:has(.sidebar.collapsed) .main-content.svelte-m62qks{left:50px}.main-content.svelte-m62qks h1.svelte-m62qks{font-size:2.5em}}.user-info.svelte-m62qks.svelte-m62qks{display:flex;align-items:flex-start;padding:0;height:3em}.logout-btn.svelte-m62qks.svelte-m62qks{padding:0;border:none;border-radius:0;background-color:var(--warning);color:white;cursor:pointer;flex-shrink:0;height:3em;width:3em;display:flex;align-items:center;justify-content:center}.user-profile-btn.svelte-m62qks.svelte-m62qks{border:0 none;border-radius:0;display:flex;align-items:center;background-color:var(--button-hover-bg);cursor:pointer;color:var(--text-color);height:3em;width:auto;min-width:3em;flex-shrink:0;line-height:1;transition:background-color 0.2s;justify-content:center;padding:1em 1em 1em 1em;margin:0}.user-profile-btn.svelte-m62qks.svelte-m62qks:hover{background-color:var(--button-bg)}.user-avatar.svelte-m62qks.svelte-m62qks,.user-avatar-placeholder.svelte-m62qks.svelte-m62qks{width:1em;height:1em;object-fit:cover}.user-avatar-placeholder.svelte-m62qks.svelte-m62qks{display:flex;align-items:center;justify-content:center;font-size:0.5em;padding:0.5em}.user-name.svelte-m62qks.svelte-m62qks{font-size:0.8rem;font-weight:500;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.drawer-overlay.svelte-m62qks.svelte-m62qks{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.5);z-index:1000;display:flex;justify-content:flex-end}.settings-drawer.svelte-m62qks.svelte-m62qks{width:640px;height:100%;background:var(--bg-color);overflow-y:auto;animation:svelte-m62qks-slideIn 0.3s ease}@keyframes svelte-m62qks-slideIn{from{transform:translateX(100%)}to{transform:translateX(0)}}.drawer-header.svelte-m62qks.svelte-m62qks{display:flex;align-items:center;justify-content:space-between;background:var(--header-bg)}.drawer-header.svelte-m62qks h2.svelte-m62qks{margin:0;color:var(--text-color);font-size:1em;padding:1rem}.close-btn.svelte-m62qks.svelte-m62qks{background:none;border:none;font-size:1em;cursor:pointer;color:var(--text-color);padding:0.5em;transition:background-color 0.2s;align-items:center}.close-btn.svelte-m62qks.svelte-m62qks:hover{background:var(--button-hover-bg)}.profile-section.svelte-m62qks.svelte-m62qks{margin-bottom:2rem}.profile-hero.svelte-m62qks.svelte-m62qks{position:relative}.profile-banner.svelte-m62qks.svelte-m62qks{width:100%;height:160px;object-fit:cover;border-radius:0;display:block}.profile-avatar.svelte-m62qks.svelte-m62qks,.profile-avatar-placeholder.svelte-m62qks.svelte-m62qks{width:72px;height:72px;border-radius:50%;object-fit:cover;flex-shrink:0;box-shadow:0 2px 8px rgba(0,0,0,0.25);border:2px solid var(--bg-color)}.overlap.svelte-m62qks.svelte-m62qks{position:absolute;left:12px;bottom:-36px;z-index:2;background:var(--button-hover-bg);display:flex;align-items:center;justify-content:center;font-size:1.5rem}.name-row.svelte-m62qks.svelte-m62qks{position:absolute;left:calc(12px + 72px + 12px);bottom:8px;right:12px;display:flex;align-items:baseline;gap:8px;z-index:1;shadow:0 3px 6px rgba(0,0,0,0.6)}.profile-username.svelte-m62qks.svelte-m62qks{margin:0;font-size:1.1rem;color:#000;text-shadow:0 3px 6px rgba(255,255,255,1)}.profile-nip05-inline.svelte-m62qks.svelte-m62qks{font-size:0.85rem;color:#000;font-family:monospace;opacity:0.95;text-shadow:0 3px 6px rgba(255,255,255,1)}.about-card.svelte-m62qks.svelte-m62qks{background:var(--header-bg);padding:12px 12px 12px 96px;position:relative;word-break:auto-phrase}.profile-about.svelte-m62qks.svelte-m62qks{margin:0;color:var(--text-color);font-size:0.9rem;line-height:1.4}@media(max-width: 640px){.settings-drawer.svelte-m62qks.svelte-m62qks{width:100%}.name-row.svelte-m62qks.svelte-m62qks{left:calc(8px + 56px + 8px);bottom:6px;right:8px;gap:6px}.profile-username.svelte-m62qks.svelte-m62qks{font-size:1rem}.profile-nip05-inline.svelte-m62qks.svelte-m62qks{font-size:0.8rem}} diff --git a/app/web/public/build/bundle.js b/app/web/public/build/bundle.js new file mode 100644 index 0000000..22087e0 --- /dev/null +++ b/app/web/public/build/bundle.js @@ -0,0 +1,2 @@ +var app=function(){"use strict";function e(){}function t(e){return e()}function n(){return Object.create(null)}function o(e){e.forEach(t)}function s(e){return"function"==typeof e}function c(e,t){return e!=e?t==t:e!==t||e&&"object"==typeof e||"function"==typeof e}let l;function r(e,t){return l||(l=document.createElement("a")),l.href=t,e===l.href}const a="undefined"!=typeof window?window:"undefined"!=typeof globalThis?globalThis:global;function i(e,t){e.appendChild(t)}function u(e,t,n){e.insertBefore(t,n||null)}function d(e){e.parentNode&&e.parentNode.removeChild(e)}function m(e){return document.createElement(e)}function f(e){return document.createTextNode(e)}function p(){return f(" ")}function g(e,t,n,o){return e.addEventListener(t,n,o),()=>e.removeEventListener(t,n,o)}function h(e){return function(t){return t.stopPropagation(),e.call(this,t)}}function b(e,t,n){null==n?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function y(e,t){t=""+t,e.data!==t&&(e.data=t)}function v(e,t){e.value=null==t?"":t}function w(e,t,n){e.classList[n?"add":"remove"](t)}let k;function $(e){k=e}function x(){const e=function(){if(!k)throw new Error("Function called outside component initialization");return k}();return(t,n,{cancelable:o=!1}={})=>{const s=e.$$.callbacks[t];if(s){const c=function(e,t,{bubbles:n=!1,cancelable:o=!1}={}){const s=document.createEvent("CustomEvent");return s.initCustomEvent(e,n,o,t),s}(t,n,{cancelable:o});return s.slice().forEach(t=>{t.call(e,c)}),!c.defaultPrevented}return!0}}function S(e,t){const n=e.$$.callbacks[t.type];n&&n.slice().forEach(e=>e.call(this,t))}const E=[],q=[];let _=[];const N=[],C=Promise.resolve();let P=!1;function T(e){_.push(e)}const I=new Set;let O=0;function z(){if(0!==O)return;const e=k;do{try{for(;O{const n=e.$$.on_mount.map(t).filter(s);e.$$.on_destroy?e.$$.on_destroy.push(...n):o(n),e.$$.on_mount=[]}),a.forEach(T)}function F(e,t){const n=e.$$;null!==n.fragment&&(!function(e){const t=[],n=[];_.forEach(o=>-1===e.indexOf(o)?t.push(o):n.push(o)),n.forEach(e=>e()),_=t}(n.after_update),o(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}function U(e,t){-1===e.$$.dirty[0]&&(E.push(e),P||(P=!0,C.then(z)),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const s=o.length?o[0]:n;return f.ctx&&r(f.ctx[e],f.ctx[e]=s)&&(!f.skip_bound&&f.bound[e]&&f.bound[e](s),p&&U(t,e)),n}):[],f.update(),p=!0,o(f.before_update),f.fragment=!!l&&l(f.ctx),s.target){if(s.hydrate){const e=function(e){return Array.from(e.childNodes)}(s.target);f.fragment&&f.fragment.l(e),e.forEach(d)}else f.fragment&&f.fragment.c();s.intro&&D(t.$$.fragment),A(t,s.target,s.anchor,s.customElement),z()}$(m)}class J{$destroy(){F(this,1),this.$destroy=e}$on(t,n){if(!s(n))return e;const o=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return o.push(n),()=>{const e=o.indexOf(n);-1!==e&&o.splice(e,1)}}$set(e){var t;this.$$set&&(t=e,0!==Object.keys(t).length)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}}const{window:K}=a;function W(e){let t,n,s,c,l,r,a,f,y,v,k,$,x,S,E,q,_,N;function C(e,t){return"extension"===e[2]?H:B}let P=C(e),T=P(e),I=e[5]&&R(e),O=e[6]&&V(e);return{c(){t=m("div"),n=m("div"),s=m("div"),c=m("h2"),c.textContent="Login to Nostr",l=p(),r=m("button"),r.textContent="×",a=p(),f=m("div"),y=m("div"),v=m("button"),v.textContent="Extension",k=p(),$=m("button"),$.textContent="Nsec",x=p(),S=m("div"),T.c(),E=p(),I&&I.c(),q=p(),O&&O.c(),b(c,"class","svelte-9yzcwg"),b(r,"class","close-btn svelte-9yzcwg"),b(s,"class","modal-header svelte-9yzcwg"),b(v,"class","tab-btn svelte-9yzcwg"),w(v,"active","extension"===e[2]),b($,"class","tab-btn svelte-9yzcwg"),w($,"active","nsec"===e[2]),b(y,"class","tabs svelte-9yzcwg"),b(S,"class","tab-content svelte-9yzcwg"),b(f,"class","tab-container svelte-9yzcwg"),b(n,"class","modal svelte-9yzcwg"),w(n,"dark-theme",e[1]),b(t,"class","modal-overlay svelte-9yzcwg"),b(t,"role","button"),b(t,"tabindex","0")},m(o,d){u(o,t,d),i(t,n),i(n,s),i(s,c),i(s,l),i(s,r),i(n,a),i(n,f),i(f,y),i(y,v),i(y,k),i(y,$),i(f,x),i(f,S),T.m(S,null),i(S,E),I&&I.m(S,null),i(S,q),O&&O.m(S,null),_||(N=[g(r,"click",e[7]),g(v,"click",e[14]),g($,"click",e[15]),g(n,"click",h(e[12])),g(n,"keydown",h(e[13])),g(t,"click",e[7]),g(t,"keydown",e[17])],_=!0)},p(e,t){4&t&&w(v,"active","extension"===e[2]),4&t&&w($,"active","nsec"===e[2]),P===(P=C(e))&&T?T.p(e,t):(T.d(1),T=P(e),T&&(T.c(),T.m(S,E))),e[5]?I?I.p(e,t):(I=R(e),I.c(),I.m(S,q)):I&&(I.d(1),I=null),e[6]?O?O.p(e,t):(O=V(e),O.c(),O.m(S,null)):O&&(O.d(1),O=null),2&t&&w(n,"dark-theme",e[1])},d(e){e&&d(t),T.d(),I&&I.d(),O&&O.d(),_=!1,o(N)}}}function B(e){let t,n,s,c,l,r,a,h,w,k,$=e[4]?"Logging in...":"Log in with nsec";return{c(){t=m("div"),n=m("p"),n.textContent="Enter your nsec (private key) to login. This will be stored securely in your browser.",s=p(),c=m("input"),l=p(),r=m("button"),a=f($),b(n,"class","svelte-9yzcwg"),b(c,"type","password"),b(c,"placeholder","nsec1..."),c.disabled=e[4],b(c,"class","nsec-input svelte-9yzcwg"),b(r,"class","login-nsec-btn svelte-9yzcwg"),r.disabled=h=e[4]||!e[3].trim(),b(t,"class","nsec-login svelte-9yzcwg")},m(o,d){u(o,t,d),i(t,n),i(t,s),i(t,c),v(c,e[3]),i(t,l),i(t,r),i(r,a),w||(k=[g(c,"input",e[16]),g(r,"click",e[10])],w=!0)},p(e,t){16&t&&(c.disabled=e[4]),8&t&&c.value!==e[3]&&v(c,e[3]),16&t&&$!==($=e[4]?"Logging in...":"Log in with nsec")&&y(a,$),24&t&&h!==(h=e[4]||!e[3].trim())&&(r.disabled=h)},d(e){e&&d(t),w=!1,o(k)}}}function H(e){let t,n,o,s,c,l,r,a=e[4]?"Connecting...":"Log in using extension";return{c(){t=m("div"),n=m("p"),n.textContent="Login using a NIP-07 compatible browser extension like nos2x or Alby.",o=p(),s=m("button"),c=f(a),b(n,"class","svelte-9yzcwg"),b(s,"class","login-extension-btn svelte-9yzcwg"),s.disabled=e[4],b(t,"class","extension-login svelte-9yzcwg")},m(a,d){u(a,t,d),i(t,n),i(t,o),i(t,s),i(s,c),l||(r=g(s,"click",e[9]),l=!0)},p(e,t){16&t&&a!==(a=e[4]?"Connecting...":"Log in using extension")&&y(c,a),16&t&&(s.disabled=e[4])},d(e){e&&d(t),l=!1,r()}}}function R(e){let t,n;return{c(){t=m("div"),n=f(e[5]),b(t,"class","message error-message svelte-9yzcwg")},m(e,o){u(e,t,o),i(t,n)},p(e,t){32&t&&y(n,e[5])},d(e){e&&d(t)}}}function V(e){let t,n;return{c(){t=m("div"),n=f(e[6]),b(t,"class","message success-message svelte-9yzcwg")},m(e,o){u(e,t,o),i(t,n)},p(e,t){64&t&&y(n,e[6])},d(e){e&&d(t)}}}function Q(t){let n,o,s,c=t[0]&&W(t);return{c(){c&&c.c(),n=f("")},m(e,l){c&&c.m(e,l),u(e,n,l),o||(s=g(K,"keydown",t[11]),o=!0)},p(e,[t]){e[0]?c?c.p(e,t):(c=W(e),c.c(),c.m(n.parentNode,n)):c&&(c.d(1),c=null)},i:e,o:e,d(e){c&&c.d(e),e&&d(n),o=!1,s()}}}function G(e,t,n){const o=x();let{showModal:s=!1}=t,{isDarkTheme:c=!1}=t,l="extension",r="",a=!1,i="",u="";function d(){n(0,s=!1),n(3,r=""),n(5,i=""),n(6,u=""),o("close")}function m(e){n(2,l=e),n(5,i=""),n(6,u="")}async function f(){n(4,a=!0),n(5,i=""),n(6,u="");try{if(!r.trim())throw new Error("Please enter your nsec");if(!(e=r.trim()).startsWith("nsec1")||(e.length<60||e.length>70))throw new Error('Invalid nsec format. Must start with "nsec1"');const t=function(e){try{return"mock_"+e.slice(5).slice(0,32)}catch(e){throw new Error("Invalid nsec format")}}(r.trim()),s="derived_"+t.slice(5,37);localStorage.setItem("nostr_auth_method","nsec"),localStorage.setItem("nostr_pubkey",s),localStorage.setItem("nostr_privkey",t),n(6,u="Successfully logged in with nsec!"),o("login",{method:"nsec",pubkey:s,privateKey:t}),setTimeout(()=>{d()},1500)}catch(e){n(5,i=e.message)}finally{n(4,a=!1)}var e}return e.$$set=e=>{"showModal"in e&&n(0,s=e.showModal),"isDarkTheme"in e&&n(1,c=e.isDarkTheme)},[s,c,l,r,a,i,u,d,m,async function(){n(4,a=!0),n(5,i=""),n(6,u="");try{if(!window.nostr)throw new Error("No Nostr extension found. Please install a NIP-07 compatible extension like nos2x or Alby.");const e=await window.nostr.getPublicKey();e&&(localStorage.setItem("nostr_auth_method","extension"),localStorage.setItem("nostr_pubkey",e),n(6,u="Successfully logged in with extension!"),o("login",{method:"extension",pubkey:e,signer:window.nostr}),setTimeout(()=>{d()},1500))}catch(e){n(5,i=e.message)}finally{n(4,a=!1)}},f,function(e){"Escape"===e.key&&d(),"Enter"===e.key&&"nsec"===l&&f()},function(t){S.call(this,e,t)},function(t){S.call(this,e,t)},()=>m("extension"),()=>m("nsec"),function(){r=this.value,n(3,r)},e=>"Escape"===e.key&&d()]}class X extends J{constructor(e){super(),j(this,e,G,Q,c,{showModal:0,isDarkTheme:1})}}const Y=["wss://relay.damus.io","wss://relay.nostr.band","wss://nos.lol","wss://relay.nostr.net","wss://relay.minibits.cash","wss://relay.coinos.io/","wss://nwc.primal.net","wss://relay.orly.dev"];const Z=new class{constructor(){this.relays=new Map,this.subscriptions=new Map}async connect(){console.log("Starting connection to",Y.length,"relays...");const e=Y.map(e=>new Promise(t=>{try{console.log(`Attempting to connect to ${e}`);const n=new WebSocket(e);n.onopen=()=>{console.log(`✓ Successfully connected to ${e}`),t(!0)},n.onerror=n=>{console.error(`✗ Error connecting to ${e}:`,n),t(!1)},n.onclose=t=>{console.warn(`Connection closed to ${e}:`,t.code,t.reason)},n.onmessage=t=>{console.log(`Message from ${e}:`,t.data);try{this.handleMessage(e,JSON.parse(t.data))}catch(n){console.error(`Failed to parse message from ${e}:`,n,t.data)}},this.relays.set(e,n),setTimeout(()=>{n.readyState!==WebSocket.OPEN&&(console.warn(`Connection timeout for ${e}`),t(!1))},5e3)}catch(n){console.error(`Failed to create WebSocket for ${e}:`,n),t(!1)}})),t=(await Promise.all(e)).filter(Boolean).length;console.log(`Connected to ${t}/${Y.length} relays`),await new Promise(e=>setTimeout(e,1e3))}handleMessage(e,t){console.log(`Processing message from ${e}:`,t);const[n,o,s,...c]=t;if(console.log(`Message type: ${n}, subscriptionId: ${o}`),"EVENT"===n)if(console.log(`Received EVENT for subscription ${o}:`,s),this.subscriptions.has(o)){console.log(`Found callback for subscription ${o}, executing...`);this.subscriptions.get(o)(s)}else console.warn(`No callback found for subscription ${o}`);else"EOSE"===n?console.log(`End of stored events for subscription ${o} from ${e}`):"NOTICE"===n?console.warn(`Notice from ${e}:`,o):console.log(`Unknown message type ${n} from ${e}:`,t)}subscribe(e,t){const n=Math.random().toString(36).substring(7);console.log(`Creating subscription ${n} with filters:`,e),this.subscriptions.set(n,t);const o=["REQ",n,e];console.log("Subscription message:",JSON.stringify(o));let s=0;for(const[e,t]of this.relays)if(console.log(`Checking relay ${e}, readyState: ${t.readyState} (${t.readyState===WebSocket.OPEN?"OPEN":"NOT OPEN"})`),t.readyState===WebSocket.OPEN)try{t.send(JSON.stringify(o)),console.log(`✓ Sent subscription to ${e}`),s++}catch(t){console.error(`✗ Failed to send subscription to ${e}:`,t)}else console.warn(`✗ Cannot send to ${e}, connection not ready`);return console.log(`Subscription ${n} sent to ${s}/${this.relays.size} relays`),n}unsubscribe(e){this.subscriptions.delete(e);const t=["CLOSE",e];for(const[e,n]of this.relays)n.readyState===WebSocket.OPEN&&n.send(JSON.stringify(t))}disconnect(){for(const[e,t]of this.relays)t.close();this.relays.clear(),this.subscriptions.clear()}},ee="events";function te(){return new Promise((e,t)=>{try{const n=indexedDB.open("nostrCache",1);n.onupgradeneeded=()=>{const e=n.result;if(!e.objectStoreNames.contains(ee)){const t=e.createObjectStore(ee,{keyPath:"id"});t.createIndex("byKindAuthor",["kind","pubkey"],{unique:!1}),t.createIndex("byKindAuthorCreated",["kind","pubkey","created_at"],{unique:!1})}},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)}catch(e){t(e)}})}function ne(e){try{const t=JSON.parse(e.content||"{}");return{name:t.name||t.display_name||"",picture:t.picture||"",banner:t.banner||"",about:t.about||"",nip05:t.nip05||"",lud16:t.lud16||t.lud06||""}}catch(e){return{name:"",picture:"",banner:"",about:"",nip05:"",lud16:""}}}async function oe(e){return new Promise(async(t,n)=>{console.log(`Starting profile fetch for pubkey: ${e}`);let o=!1,s=null,c=null,l=null,r=null;function a(){if(r)try{Z.unsubscribe(r)}catch{}c&&clearTimeout(c),l&&clearTimeout(l)}try{const n=await async function(e){try{const t=await te();return await new Promise((n,o)=>{const s=t.transaction(ee,"readonly").objectStore(ee).index("byKindAuthorCreated"),c=IDBKeyRange.bound([0,e,-1/0],[0,e,1/0]),l=s.openCursor(c,"prev");l.onsuccess=()=>{const e=l.result;n(e?e.value:null)},l.onerror=()=>o(l.error)})}catch(e){return console.warn("IDB getLatestProfileEvent failed",e),null}}(e);if(n){console.log("Using cached profile event");const e=ne(n);o=!0,t(e)}}catch(e){console.warn("Failed to load cached profile",e)}l=setTimeout(()=>{s?o||t(ne(s)):(console.log("Profile fetch timeout reached"),o||n(new Error("Profile fetch timeout"))),a()},15e3),setTimeout(()=>{console.log("Starting subscription after connection delay..."),r=Z.subscribe({kinds:[0],authors:[e]},n=>{n&&0===n.kind&&(console.log("Profile event received:",n),(!s||(n.created_at||0)>(s.created_at||0))&&(s=n),c&&clearTimeout(c),c=setTimeout(async()=>{try{if(s){await async function(e){try{const t=await te();await new Promise((n,o)=>{const s=t.transaction(ee,"readwrite");s.oncomplete=()=>n(),s.onerror=()=>o(s.error),s.objectStore(ee).put(e)})}catch(e){console.warn("IDB putEvent failed",e)}}(s);const n=ne(s);try{"undefined"!=typeof window&&window.dispatchEvent&&window.dispatchEvent(new CustomEvent("profile-updated",{detail:{pubkey:e,profile:n,event:s}}))}catch(e){console.warn("Failed to dispatch profile-updated event",e)}o||(t(n),o=!0)}}finally{a()}},800))})},2e3)})}function se(e,t,n){const o=e.slice();return o[27]=t[n],o}function ce(t){let n,o,s;return{c(){n=m("button"),n.textContent="📥",b(n,"class","login-btn svelte-m62qks")},m(e,c){u(e,n,c),o||(s=g(n,"click",t[15]),o=!0)},p:e,d(e){e&&d(n),o=!1,s()}}}function le(e){let t,n,s,c,l,r,a,h,v,w=(e[3]?.name||e[7].slice(0,8)+"...")+"";function k(e,t){return e[3]?.picture?ae:re}let $=k(e),x=$(e);return{c(){t=m("div"),n=m("button"),x.c(),s=p(),c=m("span"),l=f(w),r=p(),a=m("button"),a.textContent="🚪",b(c,"class","user-name svelte-m62qks"),b(n,"class","user-profile-btn svelte-m62qks"),b(a,"class","logout-btn svelte-m62qks"),b(t,"class","user-info svelte-m62qks")},m(o,d){u(o,t,d),i(t,n),x.m(n,null),i(n,s),i(n,c),i(c,l),i(t,r),i(t,a),h||(v=[g(n,"click",e[19]),g(a,"click",e[17])],h=!0)},p(e,t){$===($=k(e))&&x?x.p(e,t):(x.d(1),x=$(e),x&&(x.c(),x.m(n,s))),136&t&&w!==(w=(e[3]?.name||e[7].slice(0,8)+"...")+"")&&y(l,w)},d(e){e&&d(t),x.d(),h=!1,o(v)}}}function re(t){let n;return{c(){n=m("div"),n.textContent="👤",b(n,"class","user-avatar-placeholder svelte-m62qks")},m(e,t){u(e,n,t)},p:e,d(e){e&&d(n)}}}function ae(e){let t,n;return{c(){t=m("img"),r(t.src,n=e[3].picture)||b(t,"src",n),b(t,"alt","User avatar"),b(t,"class","user-avatar svelte-m62qks")},m(e,n){u(e,t,n)},p(e,o){8&o&&!r(t.src,n=e[3].picture)&&b(t,"src",n)},d(e){e&&d(t)}}}function ie(t){let n,o,s=t[27].label+"";return{c(){n=m("span"),o=f(s),b(n,"class","tab-label svelte-m62qks")},m(e,t){u(e,n,t),i(n,o)},p:e,d(e){e&&d(n)}}}function ue(e){let t,n,o,s,c,l,r,a=e[27].icon+"",h=e[4]&&ie(e);function y(){return e[23](e[27])}return{c(){t=m("button"),n=m("span"),o=f(a),s=p(),h&&h.c(),c=p(),b(n,"class","tab-icon svelte-m62qks"),b(t,"class","tab svelte-m62qks"),w(t,"active",e[2]===e[27].id)},m(e,a){u(e,t,a),i(t,n),i(n,o),i(t,s),h&&h.m(t,null),i(t,c),l||(r=g(t,"click",y),l=!0)},p(n,o){(e=n)[4]?h?h.p(e,o):(h=ie(e),h.c(),h.m(t,c)):h&&(h.d(1),h=null),2052&o&&w(t,"active",e[2]===e[27].id)},d(e){e&&d(t),h&&h.d(),l=!1,r()}}}function de(e){let t,n,s,c,l,r,a,f,y,v,k=e[3]&&me(e);return{c(){t=m("div"),n=m("div"),s=m("div"),c=m("h2"),c.textContent="Settings",l=p(),r=m("button"),r.textContent="✕",a=p(),f=m("div"),k&&k.c(),b(c,"class","svelte-m62qks"),b(r,"class","close-btn svelte-m62qks"),b(s,"class","drawer-header svelte-m62qks"),b(f,"class","drawer-content svelte-m62qks"),b(n,"class","settings-drawer svelte-m62qks"),w(n,"dark-theme",e[1]),b(t,"class","drawer-overlay svelte-m62qks"),b(t,"role","button"),b(t,"tabindex","0")},m(o,d){u(o,t,d),i(t,n),i(n,s),i(s,c),i(s,l),i(s,r),i(n,a),i(n,f),k&&k.m(f,null),y||(v=[g(r,"click",e[20]),g(n,"click",h(e[21])),g(n,"keydown",h(e[22])),g(t,"click",e[20]),g(t,"keydown",e[24])],y=!0)},p(e,t){e[3]?k?k.p(e,t):(k=me(e),k.c(),k.m(f,null)):k&&(k.d(1),k=null),2&t&&w(n,"dark-theme",e[1])},d(e){e&&d(t),k&&k.d(),y=!1,o(v)}}}function me(e){let t,n,o,s,c,l,r,a,g,h=(e[3].name||"Unknown User")+"",v=e[3].banner&&fe(e);function w(e,t){return e[3].picture?ge:pe}let k=w(e),$=k(e),x=e[3].nip05&&he(e),S=e[3].about&&be(e);return{c(){t=m("div"),n=m("div"),v&&v.c(),o=p(),$.c(),s=p(),c=m("div"),l=m("h3"),r=f(h),a=p(),x&&x.c(),g=p(),S&&S.c(),b(l,"class","profile-username svelte-m62qks"),b(c,"class","name-row svelte-m62qks"),b(n,"class","profile-hero svelte-m62qks"),b(t,"class","profile-section svelte-m62qks")},m(e,d){u(e,t,d),i(t,n),v&&v.m(n,null),i(n,o),$.m(n,null),i(n,s),i(n,c),i(c,l),i(l,r),i(c,a),x&&x.m(c,null),i(t,g),S&&S.m(t,null)},p(e,l){e[3].banner?v?v.p(e,l):(v=fe(e),v.c(),v.m(n,o)):v&&(v.d(1),v=null),k===(k=w(e))&&$?$.p(e,l):($.d(1),$=k(e),$&&($.c(),$.m(n,s))),8&l&&h!==(h=(e[3].name||"Unknown User")+"")&&y(r,h),e[3].nip05?x?x.p(e,l):(x=he(e),x.c(),x.m(c,null)):x&&(x.d(1),x=null),e[3].about?S?S.p(e,l):(S=be(e),S.c(),S.m(t,null)):S&&(S.d(1),S=null)},d(e){e&&d(t),v&&v.d(),$.d(),x&&x.d(),S&&S.d()}}}function fe(e){let t,n;return{c(){t=m("img"),r(t.src,n=e[3].banner)||b(t,"src",n),b(t,"alt","Profile banner"),b(t,"class","profile-banner svelte-m62qks")},m(e,n){u(e,t,n)},p(e,o){8&o&&!r(t.src,n=e[3].banner)&&b(t,"src",n)},d(e){e&&d(t)}}}function pe(t){let n;return{c(){n=m("div"),n.textContent="👤",b(n,"class","profile-avatar-placeholder overlap svelte-m62qks")},m(e,t){u(e,n,t)},p:e,d(e){e&&d(n)}}}function ge(e){let t,n;return{c(){t=m("img"),r(t.src,n=e[3].picture)||b(t,"src",n),b(t,"alt","User avatar"),b(t,"class","profile-avatar overlap svelte-m62qks")},m(e,n){u(e,t,n)},p(e,o){8&o&&!r(t.src,n=e[3].picture)&&b(t,"src",n)},d(e){e&&d(t)}}}function he(e){let t,n,o=e[3].nip05+"";return{c(){t=m("span"),n=f(o),b(t,"class","profile-nip05-inline svelte-m62qks")},m(e,o){u(e,t,o),i(t,n)},p(e,t){8&t&&o!==(o=e[3].nip05+"")&&y(n,o)},d(e){e&&d(t)}}}function be(e){let t,n;return{c(){t=m("div"),n=m("p"),b(n,"class","profile-about svelte-m62qks"),b(t,"class","about-card svelte-m62qks")},m(o,s){u(o,t,s),i(t,n),n.innerHTML=e[10]},p(e,t){1024&t&&(n.innerHTML=e[10])},d(e){e&&d(t)}}}function ye(e){let t,n,s,c,l,a,h,v,k,$,x,S,E,_,C,P,T,I,O,z,L,U,j,J,K,W,B,H,R,V,Q,G,Y,Z=e[9].label+"",ee=e[1]?"☀️":"🌙",te=e[4]?"◀":"▶";function ne(e,t){return e[6]?le:ce}let oe=ne(e),re=oe(e),ae=e[11],ie=[];for(let t=0;tfunction(e,t,n){const o=e.$$.props[t];void 0!==o&&(e.$$.bound[o]=n,n(e.$$.ctx[o]))}(R,"showModal",fe)),R.$on("login",e[16]),R.$on("close",e[18]),{c(){t=m("header"),n=m("div"),s=m("img"),l=p(),a=m("div"),h=m("span"),v=f(Z),k=p(),$=m("button"),x=f(ee),S=p(),re.c(),E=p(),_=m("div"),C=m("aside"),P=m("div"),T=m("div");for(let e=0;eV=!1,N.push(c)),R.$set(s)},i(e){Q||(D(R.$$.fragment,e),Q=!0)},o(e){!function(e,t,n,o){if(e&&e.o){if(M.has(e))return;M.add(e),(void 0).c.push(()=>{M.delete(e),o&&(n&&e.d(1),o())}),e.o(t)}else o&&o()}(R.$$.fragment,e),Q=!1},d(e){e&&d(t),re.d(),e&&d(E),e&&d(_),function(e,t){for(let n=0;n{"name"in e&&n(0,c=e.name)},e.$$.update=()=>{var t;8&e.$$.dirty&&n(10,o=m?.about?(t=m.about,String(t).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")).replace(/\n{2,}/g,"
"):""),4&e.$$.dirty&&n(9,s=p.find(e=>e.id===a)),2&e.$$.dirty&&"undefined"!=typeof document&&(r?document.body.classList.add("dark-theme"):document.body.classList.remove("dark-theme"))},[c,r,a,m,l,i,u,d,f,s,o,p,function(){n(4,l=!l)},function(){n(1,r=!r),"undefined"!=typeof localStorage&&localStorage.setItem("isDarkTheme",JSON.stringify(r))},g,function(){u||n(5,i=!0)},async function(e){const{method:t,pubkey:o,privateKey:s,signer:c}=e.detail;n(6,u=!0),n(7,d=o),n(5,i=!1);try{await async function(){await Z.connect()}(),n(3,m=await oe(o)),console.log("Profile loaded:",m)}catch(e){console.error("Failed to load profile:",e)}},function(){n(6,u=!1),n(7,d=""),n(3,m=null),n(8,f=!1),"undefined"!=typeof localStorage&&(localStorage.removeItem("nostr_auth_method"),localStorage.removeItem("nostr_pubkey"),localStorage.removeItem("nostr_privkey"))},function(){n(5,i=!1)},function(){n(8,f=!0)},h,function(t){S.call(this,e,t)},function(t){S.call(this,e,t)},e=>g(e.id),e=>"Escape"===e.key&&h(),function(e){i=e,n(5,i)}]}return new class extends J{constructor(e){super(),j(this,e,ve,ye,c,{name:0})}}({target:document.body,props:{name:"world"}})}(); +//# sourceMappingURL=bundle.js.map diff --git a/app/web/public/favicon.png b/app/web/public/favicon.png new file mode 100644 index 0000000..7e6f5eb Binary files /dev/null and b/app/web/public/favicon.png differ diff --git a/app/web/public/global.css b/app/web/public/global.css new file mode 100644 index 0000000..bb28a94 --- /dev/null +++ b/app/web/public/global.css @@ -0,0 +1,63 @@ +html, body { + position: relative; + width: 100%; + height: 100%; +} + +body { + color: #333; + margin: 0; + padding: 8px; + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; +} + +a { + color: rgb(0,100,200); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +a:visited { + color: rgb(0,80,160); +} + +label { + display: block; +} + +input, button, select, textarea { + font-family: inherit; + font-size: inherit; + -webkit-padding: 0.4em 0; + padding: 0.4em; + margin: 0 0 0.5em 0; + box-sizing: border-box; + border: 1px solid #ccc; + border-radius: 2px; +} + +input:disabled { + color: #ccc; +} + +button { + color: #333; + background-color: #f4f4f4; + outline: none; +} + +button:disabled { + color: #999; +} + +button:not(:disabled):active { + background-color: #ddd; +} + +button:focus { + border-color: #666; +} diff --git a/app/web/public/orly.png b/app/web/public/orly.png new file mode 100644 index 0000000..9dac94c Binary files /dev/null and b/app/web/public/orly.png differ diff --git a/app/web/readme.adoc b/app/web/readme.adoc new file mode 100644 index 0000000..1222d3e --- /dev/null +++ b/app/web/readme.adoc @@ -0,0 +1,3 @@ += nostrly.app + +a simple, material design nostr kind 1 nostr note client \ No newline at end of file diff --git a/app/web/rollup.config.js b/app/web/rollup.config.js new file mode 100644 index 0000000..1c2e15e --- /dev/null +++ b/app/web/rollup.config.js @@ -0,0 +1,78 @@ +import { spawn } from 'child_process'; +import svelte from 'rollup-plugin-svelte'; +import commonjs from '@rollup/plugin-commonjs'; +import terser from '@rollup/plugin-terser'; +import resolve from '@rollup/plugin-node-resolve'; +import livereload from 'rollup-plugin-livereload'; +import css from 'rollup-plugin-css-only'; + +const production = !process.env.ROLLUP_WATCH; + +function serve() { + let server; + + function toExit() { + if (server) server.kill(0); + } + + return { + writeBundle() { + if (server) return; + server = spawn('npm', ['run', 'start', '--', '--dev'], { + stdio: ['ignore', 'inherit', 'inherit'], + shell: true + }); + + process.on('SIGTERM', toExit); + process.on('exit', toExit); + } + }; +} + +export default { + input: 'src/main.js', + output: { + sourcemap: true, + format: 'iife', + name: 'app', + file: 'dist/bundle.js' + }, + plugins: [ + svelte({ + compilerOptions: { + // enable run-time checks when not in production + dev: !production + } + }), + // we'll extract any component CSS out into + // a separate file - better for performance + css({ output: 'bundle.css' }), + + // If you have external dependencies installed from + // npm, you'll most likely need these plugins. In + // some cases you'll need additional configuration - + // consult the documentation for details: + // https://github.com/rollup/plugins/tree/master/packages/commonjs + resolve({ + browser: true, + dedupe: ['svelte'], + exportConditions: ['svelte'] + }), + commonjs(), + + // In dev mode, call `npm run start` once + // the bundle has been generated + !production && serve(), + + // Watch the `public` directory and refresh the + // browser on changes when not in production + !production && livereload('public'), + + // If we're building for production (npm run build + // instead of npm run dev), minify + production && terser() + ], + watch: { + clearScreen: false + } +}; diff --git a/app/web/scripts/setupTypeScript.js b/app/web/scripts/setupTypeScript.js new file mode 100644 index 0000000..4385f65 --- /dev/null +++ b/app/web/scripts/setupTypeScript.js @@ -0,0 +1,134 @@ +// @ts-check + +/** This script modifies the project to support TS code in .svelte files like: + + + + As well as validating the code for CI. + */ + +/** To work on this script: + rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template +*/ + +import fs from "fs" +import path from "path" +import { argv } from "process" +import url from 'url'; + +const __filename = url.fileURLToPath(import.meta.url); +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); +const projectRoot = argv[2] || path.join(__dirname, "..") + +// Add deps to pkg.json +const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8")) +packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, { + "svelte-check": "^3.0.0", + "svelte-preprocess": "^5.0.0", + "@rollup/plugin-typescript": "^11.0.0", + "typescript": "^4.9.0", + "tslib": "^2.5.0", + "@tsconfig/svelte": "^3.0.0" +}) + +// Add script for checking +packageJSON.scripts = Object.assign(packageJSON.scripts, { + "check": "svelte-check" +}) + +// Write the package JSON +fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, " ")) + +// mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too +const beforeMainJSPath = path.join(projectRoot, "src", "main.js") +const afterMainTSPath = path.join(projectRoot, "src", "main.ts") +fs.renameSync(beforeMainJSPath, afterMainTSPath) + +// Switch the app.svelte file to use TS +const appSveltePath = path.join(projectRoot, "src", "App.svelte") +let appFile = fs.readFileSync(appSveltePath, "utf8") +appFile = appFile.replace("