useyuva 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +89 -1
  2. package/dist/esm/chat.js +2 -2
  3. package/dist/esm/docs.js +134 -0
  4. package/dist/esm/index-dj5dhqc1.js +12 -0
  5. package/dist/esm/index-e2tek7yk.js +2 -0
  6. package/dist/esm/index-jdxce2se.js +4 -0
  7. package/dist/esm/index-rdzxvj2r.js +2 -0
  8. package/dist/esm/index.js +1 -1
  9. package/dist/esm/nuxt/module.js +20 -0
  10. package/dist/esm/nuxt/runtime/components.js +88 -0
  11. package/dist/esm/react.js +1 -1
  12. package/dist/esm/thread-3ydc3xfc.js +61 -0
  13. package/dist/esm/{yuva-chat-tebg111a.js → yuva-chat-86e4mz63.js} +2 -4
  14. package/dist/types/client/api.d.ts +17 -0
  15. package/dist/types/client/client.d.ts +23 -1
  16. package/dist/types/client/reader.d.ts +12 -0
  17. package/dist/types/docs/base.d.ts +43 -0
  18. package/dist/types/docs/define.d.ts +1 -0
  19. package/dist/types/docs/discussions.d.ts +22 -0
  20. package/dist/types/docs/dom.d.ts +1 -0
  21. package/dist/types/docs/feedback.d.ts +21 -0
  22. package/dist/types/docs/page.d.ts +2 -0
  23. package/dist/types/docs/preview.d.ts +1 -0
  24. package/dist/types/docs/thread.d.ts +71 -0
  25. package/dist/types/docs.d.ts +7 -0
  26. package/dist/types/element.d.ts +0 -1
  27. package/dist/types/i18n.d.ts +1 -1
  28. package/dist/types/index.d.ts +3 -2
  29. package/dist/types/locales/en/docs.d.ts +2 -0
  30. package/dist/types/locales/en/thread.d.ts +2 -0
  31. package/dist/types/locales/tr/docs.d.ts +2 -0
  32. package/dist/types/locales/tr/thread.d.ts +2 -0
  33. package/dist/types/nuxt/module.d.ts +12 -0
  34. package/dist/types/nuxt/runtime/components.d.ts +88 -0
  35. package/dist/types/react.d.ts +26 -2
  36. package/dist/types/schema.gen.d.ts +6638 -3874
  37. package/dist/types/translate.d.ts +2 -0
  38. package/dist/types/types.d.ts +1 -0
  39. package/dist/widget/yuva-docs-thread-4580g2ny.js +61 -0
  40. package/dist/yuva-chat.js +8 -8
  41. package/dist/yuva-docs.js +144 -0
  42. package/dist/yuva.js +2 -2
  43. package/package.json +39 -8
  44. package/dist/esm/index-d1vxbqpr.js +0 -12
  45. package/dist/esm/index-r5vcg0sx.js +0 -2
package/README.md CHANGED
@@ -6,14 +6,17 @@ JavaScript SDK for [Yuva](https://github.com/productdevbook/yuva), open-source c
6
6
  |---|---|
7
7
  | `useyuva` | `createYuvaClient()`: the contact side (`/client/v1`) without DOM. Browsers, Node 22+, Bun, Deno, workers. |
8
8
  | `useyuva/chat` | The `<yuva-chat>` web component, built on that client. Browser only. |
9
+ | `useyuva/docs` | `<yuva-page-feedback>` and `<yuva-discussions>` for documentation pages. Registers in the browser only, so it is safe to import during SSR. |
9
10
  | `useyuva/react` | `YuvaProvider`, `useConversations`, `useMessages`. React 18+ is an optional peer. |
11
+ | `useyuva/nuxt` | A Nuxt module: `YuvaChat`, `YuvaPageFeedback` and `YuvaDiscussions` components, configured once in `nuxt.config`. |
10
12
  | `useyuva/api` | A typed `/v1` client for backends (API key), generated from the OpenAPI contract. |
11
13
 
12
14
  ```sh
13
15
  npm install useyuva
14
16
  ```
15
17
 
16
- Without a bundler, the server hosts the widget itself: `<script src="https://<your-yuva>/yuva.js" defer></script>`.
18
+ Without a bundler, the server hosts the widget itself: `<script src="https://<your-yuva>/yuva.js" defer></script>`,
19
+ and the documentation page elements without the chat: `<script src="https://<your-yuva>/yuva-docs.js" defer></script>`.
17
20
 
18
21
  ## Client
19
22
 
@@ -65,6 +68,48 @@ Realtime:
65
68
 
66
69
  Errors are `ApiError` with `status` and the problem `code` (`status` 0 and `network` when offline).
67
70
 
71
+ ### Discussions
72
+
73
+ The threads of documentation pages, the same calls `<yuva-discussions>` makes. Reading needs no
74
+ session; writing needs a reader (signed in with their account on the Yuva server) or a contact
75
+ session started with an identity token. Bodies are Markdown; show `body_html`, which the server
76
+ renders and sanitizes, never the Markdown as HTML.
77
+
78
+ ```ts
79
+ const yuva = createYuvaClient({ server, channel });
80
+
81
+ const { items, next_cursor } = await yuva.listDiscussions(location.href, { sort: "top" });
82
+
83
+ button.onclick = async () => {
84
+ // Opens <server>/account/connect in a popup; call it from the click handler itself.
85
+ const { reader } = await yuva.readerSignIn();
86
+ console.log(reader.initials);
87
+ };
88
+
89
+ const thread = await yuva.startDiscussion({ page_url: location.href, page_title: document.title, title, body });
90
+ const { discussion, posts } = await yuva.getDiscussion(thread.id);
91
+ const reply = await yuva.replyToDiscussion(thread.id, { body: "Same here", parent_post_id: posts[0]?.id });
92
+ ```
93
+
94
+ - `listDiscussions(page, { sort, cursor, limit })` → `DiscussionPage`. `sort` is `new` (default), `top` or `unanswered`.
95
+ - `getDiscussion(id)` → `DiscussionDetail` (`{ discussion, posts }`, the accepted answer first, then oldest first; replies to replies carry `parent_post_id`).
96
+ - `startDiscussion({ page_url, page_title, title, body })` → `Discussion`; `replyToDiscussion(id, { body, parent_post_id })` → `DiscussionPost`. On a moderated channel a reader's first posts come back with `state: "pending"`, visible only to them until a member approves one.
97
+ - `editDiscussionPost(postId, { body, title })` (within 30 minutes; `title` on the opening post only), `deleteDiscussionPost(postId)`.
98
+ - `voteDiscussionPost(postId, voted = true)` → the post with its count; the thread's votes are its opening post's (`opening_post_id`).
99
+ - `followDiscussion(id, following = true)`; `acceptDiscussionAnswer(id, postId | null)` (the thread's author; `null` clears it) → `Discussion`.
100
+ - `channelSettings()` tells whether discussions are on: `chat.discussions` is `off`, `moderated` or `open`.
101
+ - With a reader or identified contact, lists and threads also carry the caller's pending posts and `mine`, `voted` and `following`.
102
+
103
+ Reader sign-in:
104
+
105
+ - `readerSignIn({ width, height })` opens `<server>/account/connect?channel=<key>&origin=<origin>` in a popup and resolves with `{ token, expires_at, reader: { name, initials } }` once the popup posts it back. Only a message from the server's origin, for this channel and this page's origin, is taken. Rejects with `ApiError` code `popup_blocked` or `sign_in_cancelled`.
106
+ - The token is kept in `storage` per server and channel until it expires (24 hours); `reader` returns it or `null`. On `401` it is dropped and writes fail with `sign_in_required`; sign in again (the popup closes at once while the account is still signed in on the server).
107
+ - `readerSignOut()` ends the token on the server and forgets it. `on("reader", handler)` reports sign-in and sign-out.
108
+ - `discussionWriter()` resolves to `"reader"`, `"contact"` (an identity token) or `null` (read only).
109
+ - `openReaderPopup(server, channel)` and `readerConnectUrl(server, channel, origin)` are the same popup without a client.
110
+
111
+ Errors worth handling: `403 sign_in_required`, `discussions_off`, `discussion_locked`, `edit_window_closed`, `forbidden`; `400 page_origin`; `429 rate_limited`.
112
+
68
113
  ## Chat element
69
114
 
70
115
  ```ts
@@ -77,6 +122,49 @@ import "useyuva/chat";
77
122
 
78
123
  Attributes, methods and events are in [docs/widget.md](https://github.com/productdevbook/yuva/blob/main/docs/widget.md).
79
124
 
125
+ ## Documentation pages
126
+
127
+ ```ts
128
+ import "useyuva/docs";
129
+ ```
130
+
131
+ ```html
132
+ <yuva-page-feedback channel="yuva_pk_…" server="https://support.example.com"></yuva-page-feedback>
133
+ <yuva-discussions channel="yuva_pk_…" server="https://support.example.com"></yuva-discussions>
134
+ ```
135
+
136
+ Both take `channel`, `server`, `locale`, `dir` and `identity-token` like `<yuva-chat>`, plus `page`
137
+ and `page-title`, which default to the URL without query and fragment and `document.title`. They
138
+ follow client-side navigation.
139
+
140
+ `<yuva-discussions>` shows the page's threads (New, Top, Unanswered), a thread with its replies
141
+ and accepted answer, and forms to start a thread, reply, edit, delete, vote, follow and accept an
142
+ answer. Readers sign in with their account on the Yuva server in a popup; with `identity-token`
143
+ your signed-in users write as themselves without it. Methods: `reload()`, `open(id)`, `signIn()`
144
+ (from a click), `signOut()`.
145
+
146
+ Events: `yuva-rating` (`{ page, rating, previous }`), `yuva-feedback` (`{ page, rating, conversation_id }`),
147
+ `yuva-discussion` (`{ page, discussion_id, state }`), `yuva-post` (`{ page, discussion_id, post_id, state }`)
148
+ and `yuva-reader` (`{ name, initials }` or `null`). In React use `YuvaPageFeedback` and
149
+ `YuvaDiscussions` from `useyuva/react` (`onDiscussion`, `onPost`, `onReader`).
150
+
151
+ ## Nuxt
152
+
153
+ ```ts
154
+ // nuxt.config.ts
155
+ export default defineNuxtConfig({
156
+ modules: ["useyuva/nuxt"],
157
+ yuva: { server: "https://support.example.com", channel: "yuva_pk_…" },
158
+ });
159
+ ```
160
+
161
+ `NUXT_PUBLIC_YUVA_SERVER`, `NUXT_PUBLIC_YUVA_CHANNEL` and `NUXT_PUBLIC_YUVA_LOCALE` override the
162
+ options at runtime. `<YuvaChat />`, `<YuvaPageFeedback />` and `<YuvaDiscussions />` are
163
+ auto-imported, render on the server and load their element in the browser when they mount. Props
164
+ are the attributes in camelCase (`pageTitle`, `identityToken`, which also takes a function);
165
+ events are `@rating`, `@feedback`, `@discussion`, `@post`, `@reader` and `@unread`. `yuva-*` tags are custom elements in
166
+ your templates.
167
+
80
168
  ## React
81
169
 
82
170
  ```tsx
package/dist/esm/chat.js CHANGED
@@ -1,4 +1,4 @@
1
- import{launcherOf,readStored,writeStored}from"./index-r5vcg0sx.js";var messages=JSON.parse('{"6I7cJR":["Open chat"],"OkHvGB":["Open chat, ",["count"]," unread"],"pqZJT2":["Close chat"]}');var messages2=JSON.parse('{"6I7cJR":["Sohbeti aç"],"OkHvGB":["Sohbeti aç, ",["count"]," okunmamış"],"pqZJT2":["Sohbeti kapat"]}');var catalogs={en:messages,tr:messages2},rtlLanguages=new Set(["ar","fa","he","ur"]),sourceLocale="en";function resolveLocale(...candidates){for(let candidate of candidates){if(!candidate)continue;let tag=candidate.toLowerCase();if(catalogs[tag])return tag;let language=tag.split(/[-_]/)[0]??"";if(catalogs[language])return language}return sourceLocale}function directionOf(locale){return rtlLanguages.has(locale.split(/[-_]/)[0]??"")?"rtl":"ltr"}function catalogFor(locale){return catalogs[locale]??messages}function translatePlain(messages2,descriptor){let entry=messages2[descriptor.id]??messages[descriptor.id],values=descriptor.values??{};if(typeof entry==="string")return entry;if(Array.isArray(entry))return entry.map((part)=>typeof part==="string"?part:Array.isArray(part)?String(values[part[0]]??""):"").join("");return descriptor.message??descriptor.id}var styles=`
1
+ import{resolveLocale,directionOf,catalogFor,translatePlain,launcherOf,contrastOn,readStored,writeStored}from"./index-e2tek7yk.js";var styles=`
2
2
  :host {
3
3
  --yuva-accent: #2563eb;
4
4
  --yuva-on-accent: #ffffff;
@@ -106,4 +106,4 @@ import{launcherOf,readStored,writeStored}from"./index-r5vcg0sx.js";var messages=
106
106
  block-size: 100%;
107
107
  min-block-size: 320px;
108
108
  }
109
- `,chatIcon='<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M4 5h16v11H9l-5 4z"/></svg>',closeIcon='<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>',scriptSrc=document.currentScript instanceof HTMLScriptElement?document.currentScript.src:"";function contrastOn(color){let n=Number.parseInt(color.slice(1),16),[r,g,b]=[n>>16&255,n>>8&255,n&255].map((c)=>{let s=c/255;return s<=0.03928?s/12.92:((s+0.055)/1.055)**2.4});return 0.2126*r+0.7152*g+0.0722*b>0.4?"#111827":"#ffffff"}class YuvaChatElement extends HTMLElement{static observedAttributes=["channel","server","layout","locale","dir","identity-token","open"];static loadPanel=()=>import(new URL("yuva-chat.js",scriptSrc||location.href).href);#root;#launcher=null;#badge=null;#panel;#controller=null;#messages={};#open=!1;#unread=0;#identity=null;#refused=!1;constructor(){super();this.#root=this.attachShadow({mode:"open"}),this.#panel=document.createElement("section"),this.#panel.className="panel",this.#panel.setAttribute("role","dialog"),this.#panel.hidden=!0,this.#panel.addEventListener("keydown",(event)=>{if(event.key==="Escape"&&this.layout==="launcher")this.close()})}get layout(){return this.getAttribute("layout")==="embedded"?"embedded":"launcher"}get channel(){return this.getAttribute("channel")}get server(){return(this.getAttribute("server")||(scriptSrc?new URL(scriptSrc).origin:location.origin)).replace(/\/+$/,"")}get locale(){return resolveLocale(this.getAttribute("locale"),navigator.language)}get isOpen(){return this.#open}get unread(){return this.#unread}setIdentityToken(source){if(this.#identity=source,this.#controller)this.#controller.then((controller)=>controller.identityChanged())}async signOut(){if(this.#identity=null,this.removeAttribute("identity-token"),this.#controller)await(await this.#controller).signOut()}async rate(conversationId,rating,comment){return(await this.#ensureController()).rate(conversationId,rating,comment)}connectedCallback(){this.#render();let channel=this.channel;if(this.layout==="embedded"||this.hasAttribute("open")||!channel)return;let stored=readStored(channel);if(stored.launcher)this.#applyLauncher(stored.launcher);let idle=()=>{if(!this.isConnected||this.#controller||this.channel!==channel)return;if(stored.session)this.#ensureController();else this.#styleLauncher(channel)};if("requestIdleCallback"in window)requestIdleCallback(idle,{timeout:3000});else setTimeout(idle,1000)}#styleLauncher(channel){fetch(`${this.server}/client/v1/channels/${encodeURIComponent(channel)}`,{credentials:"omit"}).then((response)=>{if(response.status===403||response.status===404)this.#refuse();return response.ok?response.json():null}).then((inbox)=>{if(!inbox||this.channel!==channel||this.#controller)return;let launcher=launcherOf(inbox);this.#applyLauncher(launcher),writeStored(channel,{...readStored(channel),launcher})}).catch(()=>{return})}disconnectedCallback(){let controller=this.#controller;if(this.#controller=null,controller)controller.then((c)=>c.destroy())}attributeChangedCallback(name,previous,value){if(!this.isConnected||previous===value)return;if(name==="open"){value===null?this.close():this.open();return}if(name==="identity-token"){if(this.#controller)this.#controller.then((controller)=>controller.identityChanged());return}if(name==="channel"||name==="server"){this.disconnectedCallback(),this.#refused=!1,this.#render();return}if(this.#render(),this.#controller)this.#controller.then((controller)=>controller.update())}async open(){if(this.#open||this.#refused)return;this.#open=!0,this.toggleAttribute("open-panel",!0),this.#syncLauncher();let controller=await this.#ensureController();if(!this.#open)return;this.#panel.hidden=!1,controller.opened()}close(){if(!this.#open||this.layout==="embedded")return;if(this.#open=!1,this.toggleAttribute("open-panel",!1),this.#panel.hidden=!0,this.#syncLauncher(),this.#launcher?.focus(),this.#controller)this.#controller.then((controller)=>controller.closed())}toggle(){return this.#open?this.close():this.open()}#config(){return{channel:this.channel,server:this.server,layout:this.layout,locale:this.locale}}#ensureController(){return this.#controller??=YuvaChatElement.loadPanel().then(({mount})=>mount({panel:this.#panel,config:()=>this.#config(),identityToken:async()=>this.#identity?await this.#identity()??null:this.getAttribute("identity-token"),isOpen:()=>this.#open,close:()=>this.close(),setUnread:(count)=>{this.#unread=count,this.#syncLauncher(),this.dispatchEvent(new CustomEvent("yuva-unread",{detail:{count}}))},setLauncher:(style)=>this.#applyLauncher(style),refused:()=>this.#refuse()})),this.#controller}#refuse(){if(this.#refused)return;this.#refused=!0,console.warn(`Yuva: channel ${this.channel} is unknown or does not allow ${location.origin}`),this.#open=!1,this.toggleAttribute("open-panel",!1),this.disconnectedCallback(),this.#render()}#applyLauncher(style){if(this.setAttribute("position",style.position==="left"?"left":"right"),style.color&&/^#[0-9a-f]{6}$/i.test(style.color))this.style.setProperty("--yuva-accent",style.color),this.style.setProperty("--yuva-on-accent",contrastOn(style.color))}#render(){let locale=this.locale;this.#messages=catalogFor(locale),this.setAttribute("lang",locale);let style=document.createElement("style");if(style.textContent=styles,this.#panel.dir=this.getAttribute("dir")??directionOf(locale),this.#panel.setAttribute("aria-modal","false"),this.#refused){this.#launcher=null,this.#badge=null,this.#root.replaceChildren();return}if(this.layout==="embedded"){this.#launcher=null,this.#badge=null,this.#root.replaceChildren(style,this.#panel),this.#open=!1,this.open();return}let launcher=document.createElement("button");if(launcher.type="button",launcher.className="launcher",launcher.addEventListener("click",()=>void this.toggle()),this.#launcher=launcher,this.#badge=document.createElement("span"),this.#badge.className="badge",this.#badge.setAttribute("aria-hidden","true"),this.#root.replaceChildren(style,this.#panel,launcher),this.#syncLauncher(),this.hasAttribute("open")&&!this.#open)this.open()}#syncLauncher(){let launcher=this.#launcher,badge=this.#badge;if(!launcher||!badge)return;let count=this.#unread;launcher.setAttribute("aria-expanded",String(this.#open)),launcher.setAttribute("aria-label",this.#open?translatePlain(this.#messages,{id:"pqZJT2"}):count>0?translatePlain(this.#messages,{id:"OkHvGB",values:{count}}):translatePlain(this.#messages,{id:"6I7cJR"})),launcher.innerHTML=this.#open?closeIcon:chatIcon,badge.textContent=count>9?"9+":String(count),badge.hidden=this.#open||count===0,launcher.append(badge)}}function define(){if(!customElements.get("yuva-chat"))customElements.define("yuva-chat",YuvaChatElement)}YuvaChatElement.loadPanel=()=>import("./yuva-chat-tebg111a.js");define();export{YuvaChatElement,define};
109
+ `,chatIcon='<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M4 5h16v11H9l-5 4z"/></svg>',closeIcon='<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>',scriptSrc=document.currentScript instanceof HTMLScriptElement?document.currentScript.src:"";class YuvaChatElement extends HTMLElement{static observedAttributes=["channel","server","layout","locale","dir","identity-token","open"];static loadPanel=()=>import(new URL("yuva-chat.js",scriptSrc||location.href).href);#root;#launcher=null;#badge=null;#panel;#controller=null;#messages={};#open=!1;#unread=0;#identity=null;#refused=!1;constructor(){super();this.#root=this.attachShadow({mode:"open"}),this.#panel=document.createElement("section"),this.#panel.className="panel",this.#panel.setAttribute("role","dialog"),this.#panel.hidden=!0,this.#panel.addEventListener("keydown",(event)=>{if(event.key==="Escape"&&this.layout==="launcher")this.close()})}get layout(){return this.getAttribute("layout")==="embedded"?"embedded":"launcher"}get channel(){return this.getAttribute("channel")}get server(){return(this.getAttribute("server")||(scriptSrc?new URL(scriptSrc).origin:location.origin)).replace(/\/+$/,"")}get locale(){return resolveLocale(this.getAttribute("locale"),navigator.language)}get isOpen(){return this.#open}get unread(){return this.#unread}setIdentityToken(source){if(this.#identity=source,this.#controller)this.#controller.then((controller)=>controller.identityChanged())}async signOut(){if(this.#identity=null,this.removeAttribute("identity-token"),this.#controller)await(await this.#controller).signOut()}async rate(conversationId,rating,comment){return(await this.#ensureController()).rate(conversationId,rating,comment)}connectedCallback(){this.#render();let channel=this.channel;if(this.layout==="embedded"||this.hasAttribute("open")||!channel)return;let stored=readStored(channel);if(stored.launcher)this.#applyLauncher(stored.launcher);let idle=()=>{if(!this.isConnected||this.#controller||this.channel!==channel)return;if(stored.session)this.#ensureController();else this.#styleLauncher(channel)};if("requestIdleCallback"in window)requestIdleCallback(idle,{timeout:3000});else setTimeout(idle,1000)}#styleLauncher(channel){fetch(`${this.server}/client/v1/channels/${encodeURIComponent(channel)}`,{credentials:"omit"}).then((response)=>{if(response.status===403||response.status===404)this.#refuse();return response.ok?response.json():null}).then((inbox)=>{if(!inbox||this.channel!==channel||this.#controller)return;let launcher=launcherOf(inbox);this.#applyLauncher(launcher),writeStored(channel,{...readStored(channel),launcher})}).catch(()=>{return})}disconnectedCallback(){let controller=this.#controller;if(this.#controller=null,controller)controller.then((c)=>c.destroy())}attributeChangedCallback(name,previous,value){if(!this.isConnected||previous===value)return;if(name==="open"){value===null?this.close():this.open();return}if(name==="identity-token"){if(this.#controller)this.#controller.then((controller)=>controller.identityChanged());return}if(name==="channel"||name==="server"){this.disconnectedCallback(),this.#refused=!1,this.#render();return}if(this.#render(),this.#controller)this.#controller.then((controller)=>controller.update())}async open(){if(this.#open||this.#refused)return;this.#open=!0,this.toggleAttribute("open-panel",!0),this.#syncLauncher();let controller=await this.#ensureController();if(!this.#open)return;this.#panel.hidden=!1,controller.opened()}close(){if(!this.#open||this.layout==="embedded")return;if(this.#open=!1,this.toggleAttribute("open-panel",!1),this.#panel.hidden=!0,this.#syncLauncher(),this.#launcher?.focus(),this.#controller)this.#controller.then((controller)=>controller.closed())}toggle(){return this.#open?this.close():this.open()}#config(){return{channel:this.channel,server:this.server,layout:this.layout,locale:this.locale}}#ensureController(){return this.#controller??=YuvaChatElement.loadPanel().then(({mount})=>mount({panel:this.#panel,config:()=>this.#config(),identityToken:async()=>this.#identity?await this.#identity()??null:this.getAttribute("identity-token"),isOpen:()=>this.#open,close:()=>this.close(),setUnread:(count)=>{this.#unread=count,this.#syncLauncher(),this.dispatchEvent(new CustomEvent("yuva-unread",{detail:{count}}))},setLauncher:(style)=>this.#applyLauncher(style),refused:()=>this.#refuse()})),this.#controller}#refuse(){if(this.#refused)return;this.#refused=!0,console.warn(`Yuva: channel ${this.channel} is unknown or does not allow ${location.origin}`),this.#open=!1,this.toggleAttribute("open-panel",!1),this.disconnectedCallback(),this.#render()}#applyLauncher(style){if(this.setAttribute("position",style.position==="left"?"left":"right"),style.color&&/^#[0-9a-f]{6}$/i.test(style.color))this.style.setProperty("--yuva-accent",style.color),this.style.setProperty("--yuva-on-accent",contrastOn(style.color))}#render(){let locale=this.locale;this.#messages=catalogFor(locale),this.setAttribute("lang",locale);let style=document.createElement("style");if(style.textContent=styles,this.#panel.dir=this.getAttribute("dir")??directionOf(locale),this.#panel.setAttribute("aria-modal","false"),this.#refused){this.#launcher=null,this.#badge=null,this.#root.replaceChildren();return}if(this.layout==="embedded"){this.#launcher=null,this.#badge=null,this.#root.replaceChildren(style,this.#panel),this.#open=!1,this.open();return}let launcher=document.createElement("button");if(launcher.type="button",launcher.className="launcher",launcher.addEventListener("click",()=>void this.toggle()),this.#launcher=launcher,this.#badge=document.createElement("span"),this.#badge.className="badge",this.#badge.setAttribute("aria-hidden","true"),this.#root.replaceChildren(style,this.#panel,launcher),this.#syncLauncher(),this.hasAttribute("open")&&!this.#open)this.open()}#syncLauncher(){let launcher=this.#launcher,badge=this.#badge;if(!launcher||!badge)return;let count=this.#unread;launcher.setAttribute("aria-expanded",String(this.#open)),launcher.setAttribute("aria-label",this.#open?translatePlain(this.#messages,{id:"pqZJT2"}):count>0?translatePlain(this.#messages,{id:"OkHvGB",values:{count}}):translatePlain(this.#messages,{id:"6I7cJR"})),launcher.innerHTML=this.#open?closeIcon:chatIcon,badge.textContent=count>9?"9+":String(count),badge.hidden=this.#open||count===0,launcher.append(badge)}}function define(){if(!customElements.get("yuva-chat"))customElements.define("yuva-chat",YuvaChatElement)}YuvaChatElement.loadPanel=()=>import("./yuva-chat-86e4mz63.js");define();export{YuvaChatElement,define};
@@ -0,0 +1,134 @@
1
+ import{resolveLocale,directionOf,translatePlain,launcherOf,contrastOn,readStored,writeStored}from"./index-e2tek7yk.js";import{ApiError2,createYuvaClient2}from"./index-dj5dhqc1.js";import{el}from"./index-rdzxvj2r.js";var messages=JSON.parse(`{"-UxBoG":["Deleted member"],"1UzENP":["No"],"2lF8Zj":["Unanswered"],"5lWFkC":["Sign in"],"78dEKp":["Sort discussions"],"7oGBMv":["Discussions"],"8Ekoev":["You can't do that."],"8ntmc1":["Votes: ",["0"]],"D16wQE":["Could not open the discussion. Try again."],"D9CFV5":["Anonymous"],"E_qvzP":["Enter a valid e-mail address."],"EmiQvf":["Thanks! Anything we could add? (optional)"],"FMWGhb":["Deleted reader"],"G2fuEb":["Locked"],"GQVIIl":["You seem to be offline."],"GqCA_t":["Answered"],"Hw3P48":["Replies: ",["0"]],"JlFcis":["Send"],"KDw4GX":["Try again"],"KEjhwb":["This discussion is locked."],"KM6m8p":["Team"],"LwDe8g":["Sign in to take part in the discussion."],"MDmQKn":["Could not load the discussions."],"Pwqkdw":["Loading…"],"SOvBw9":["Was this page helpful?"],"Sad2tK":["Sending…"],"Su3Vyp":["Check the title and the text and try again."],"U7H5Fm":["Sorry about that. What was missing or unclear?"],"UbRKMZ":["Pending"],"WWy8TN":["Top"],"XJURKP":["E-mail address"],"a_qtPP":["E-mail for a reply (optional)"],"b6Orhy":["Have a question or an idea? Start the first one."],"b_f4xj":["Posts can be edited for 30 minutes after they are written."],"d6N994":["Thanks for your feedback."],"fMPkxb":["Show more"],"fcWrnU":["Sign out"],"isRobC":["New"],"j-3eim":["Discussions about this page"],"l75CjT":["Yes"],"lWv8id":["Awaiting approval"],"nLqMRA":["Team member"],"nW3go1":["Your browser blocked the sign-in window. Allow pop-ups for this site and try again."],"pzatAP":["Loading discussions…"],"rpfOwz":["No discussions about this page yet."],"tA4dAO":["No thanks"],"tAL5wg":["Could not send. Try again."],"tayT8P":["Discussions are turned off for this site."],"twEQ41":["Every discussion on this page has an answer."],"tzsBOP":["Too many attempts. Wait a moment and try again."],"uuIskV":["Sign in to write to us."],"xOTzt5":["just now"],"z1qf6l":["Start a discussion"],"zmA6sS":["Reader"]}`);var messages2=JSON.parse('{"-UxBoG":["Silinmiş üye"],"1UzENP":["Hayır"],"2lF8Zj":["Yanıtsız"],"5lWFkC":["Giriş yap"],"78dEKp":["Tartışmaları sırala"],"7oGBMv":["Tartışmalar"],"8Ekoev":["Bunu yapamazsınız."],"8ntmc1":["Oylar: ",["0"]],"D16wQE":["Tartışma açılamadı. Yeniden deneyin."],"D9CFV5":["Anonim"],"E_qvzP":["Geçerli bir e-posta adresi girin."],"EmiQvf":["Teşekkürler! Ekleyebileceğimiz bir şey var mı? (isteğe bağlı)"],"FMWGhb":["Silinmiş okuyucu"],"G2fuEb":["Kilitli"],"GQVIIl":["Çevrimdışı görünüyorsunuz."],"GqCA_t":["Yanıtlandı"],"Hw3P48":["Yanıtlar: ",["0"]],"JlFcis":["Gönder"],"KDw4GX":["Yeniden dene"],"KEjhwb":["Bu tartışma kilitli."],"KM6m8p":["Ekip"],"LwDe8g":["Tartışmaya katılmak için giriş yapın."],"MDmQKn":["Tartışmalar yüklenemedi."],"Pwqkdw":["Yükleniyor…"],"SOvBw9":["Bu sayfa işinize yaradı mı?"],"Sad2tK":["Gönderiliyor…"],"Su3Vyp":["Başlığı ve metni kontrol edip yeniden deneyin."],"U7H5Fm":["Üzgünüz. Eksik ya da anlaşılmayan ne vardı?"],"UbRKMZ":["Bekliyor"],"WWy8TN":["En çok oy alan"],"XJURKP":["E-posta adresi"],"a_qtPP":["Yanıt için e-posta (isteğe bağlı)"],"b6Orhy":["Bir sorunuz ya da fikriniz mi var? İlk tartışmayı siz başlatın."],"b_f4xj":["Gönderiler yazıldıktan sonra 30 dakika içinde düzenlenebilir."],"d6N994":["Geri bildiriminiz için teşekkürler."],"fMPkxb":["Daha fazla göster"],"fcWrnU":["Çıkış yap"],"isRobC":["Yeni"],"j-3eim":["Bu sayfayla ilgili tartışmalar"],"l75CjT":["Evet"],"lWv8id":["Onay bekliyor"],"nLqMRA":["Ekip üyesi"],"nW3go1":["Tarayıcınız giriş penceresini engelledi. Bu site için açılır pencerelere izin verip yeniden deneyin."],"pzatAP":["Tartışmalar yükleniyor…"],"rpfOwz":["Bu sayfayla ilgili henüz bir tartışma yok."],"tA4dAO":["Hayır, teşekkürler"],"tAL5wg":["Gönderilemedi. Yeniden deneyin."],"tayT8P":["Bu sitede tartışmalar kapalı."],"twEQ41":["Bu sayfadaki her tartışmanın bir yanıtı var."],"tzsBOP":["Çok fazla deneme. Biraz bekleyip tekrar deneyin."],"uuIskV":["Bize yazmak için oturum açın."],"xOTzt5":["az önce"],"z1qf6l":["Tartışma başlat"],"zmA6sS":["Okur"]}');var shared=Symbol.for("yuva.navigation");function navigation(){let win=window,target=win[shared];if(target)return target;let created=new EventTarget;target=win[shared]=created;let notify=()=>created.dispatchEvent(new Event("change"));for(let name of["pushState","replaceState"]){let original=history[name];history[name]=function(...args){let result=original.apply(this,args);return notify(),result}}return window.addEventListener("popstate",notify),window.addEventListener("hashchange",notify),target}function pageOf(value){try{let url=new URL(value||location.href,location.href);return url.search="",url.hash="",url.href}catch{return value||location.href}}function followPage(listener){let target=navigation(),scheduled=!1,changed=()=>{if(scheduled)return;scheduled=!0,queueMicrotask(()=>{scheduled=!1,listener()})};return target.addEventListener("change",changed),()=>target.removeEventListener("change",changed)}var catalogs={en:messages,tr:messages2},settings=new Map,Base=typeof HTMLElement>"u"?class{}:HTMLElement,scriptSrc=typeof document<"u"&&document.currentScript instanceof HTMLScriptElement?document.currentScript.src:"",baseStyles=`
2
+ :host {
3
+ --yuva-accent: #2563eb;
4
+ --yuva-on-accent: #ffffff;
5
+ --yuva-border: rgb(127 127 127 / 0.35);
6
+ --yuva-soft: rgb(127 127 127 / 0.1);
7
+ --yuva-danger: #dc2626;
8
+ display: block;
9
+ color: inherit;
10
+ font: inherit;
11
+ line-height: 1.5;
12
+ }
13
+ :host([hidden]) { display: none; }
14
+ @media (prefers-color-scheme: dark) {
15
+ :host { --yuva-danger: #f87171; }
16
+ }
17
+ [hidden] { display: none !important; }
18
+ * { box-sizing: border-box; }
19
+ p { margin: 0; }
20
+ .muted { opacity: 0.72; font-size: 0.9em; }
21
+ .error { color: var(--yuva-danger); font-size: 0.9em; }
22
+ form { display: flex; flex-direction: column; gap: 8px; }
23
+ textarea, input {
24
+ inline-size: 100%;
25
+ padding: 8px 10px;
26
+ border: 1px solid var(--yuva-border);
27
+ border-radius: 8px;
28
+ background: transparent;
29
+ color: inherit;
30
+ font: inherit;
31
+ }
32
+ textarea { resize: vertical; min-block-size: 72px; }
33
+ textarea:focus-visible, input:focus-visible { outline: 2px solid var(--yuva-accent); outline-offset: -1px; border-color: transparent; }
34
+ .actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: flex-end; }
35
+ .send {
36
+ padding: 8px 14px;
37
+ border: 0;
38
+ border-radius: 8px;
39
+ background: var(--yuva-accent);
40
+ color: var(--yuva-on-accent);
41
+ font: inherit;
42
+ font-weight: 600;
43
+ cursor: pointer;
44
+ }
45
+ .send:disabled { opacity: 0.6; cursor: default; }
46
+ .send:focus-visible, .link:focus-visible { outline: 2px solid var(--yuva-accent); outline-offset: 2px; }
47
+ .link { padding: 0; border: 0; background: none; color: inherit; font: inherit; text-decoration: underline; cursor: pointer; opacity: 0.8; }
48
+ @media (max-width: 480px) { textarea, input { font-size: 16px; } }
49
+ `;class YuvaDocsElement extends Base{static observedAttributes=["channel","server","locale","dir","page","page-title","identity-token"];root;inbox=null;pageUrl="";refused=!1;direction="ltr";#messages=messages;#client=null;#identity=null;#unfollow=null;#settingsFor="";#connected=!1;#accentColor="";constructor(){super();this.root=this.attachShadow({mode:"open"})}get channel(){return this.getAttribute("channel")}set channel(value){this.#attribute("channel",value)}get server(){return(this.getAttribute("server")||(scriptSrc?new URL(scriptSrc).origin:location.origin)).replace(/\/+$/,"")}set server(value){this.#attribute("server",value)}get locale(){return resolveLocale(this.getAttribute("locale"),navigator.language)}set locale(value){this.#attribute("locale",value)}get page(){return this.pageUrl||pageOf(this.getAttribute("page"))}set page(value){this.#attribute("page",value)}get pageTitle(){return this.getAttribute("page-title")||document.title}set pageTitle(value){this.#attribute("page-title",value)}#attribute(name,value){if(value===null||value===void 0||value==="")this.removeAttribute(name);else this.setAttribute(name,value)}setIdentityToken(source){if(this.#identity=source,this.#client?.setIdentityToken(()=>this.#identityToken()),this.#connected&&!this.refused)this.render()}connectedCallback(){this.#connected=!0,this.#unfollow??=followPage(()=>this.#syncPage()),this.pageUrl=pageOf(this.getAttribute("page")),this.#applyLocale(),this.#loadSettings(),this.render(),this.pageChanged()}disconnectedCallback(){this.#connected=!1,this.#unfollow?.(),this.#unfollow=null}attributeChangedCallback(name,previous,value){if(!this.#connected||previous===value)return;if(name==="channel"||name==="server"){this.#client?.reset(),this.#client=null,this.refused=!1,this.inbox=null,this.#settingsFor="",this.#loadSettings(),this.reset(),this.render(),this.pageChanged();return}if(name==="page")return this.#syncPage();if(name==="identity-token")this.#client?.setIdentityToken(()=>this.#identityToken());if(name==="locale"||name==="dir")this.#applyLocale();this.render()}t(descriptor){return translatePlain(this.#messages,descriptor,messages)}client(){return this.#client??=createYuvaClient2({server:this.server,channel:this.channel??"",identityToken:()=>this.#identityToken()}),this.#client}get identified(){return!!this.#identity||this.hasAttribute("identity-token")}get canWrite(){return this.identified||this.inbox?.chat.allow_anonymous!==!1}emit(type,detail){this.dispatchEvent(new CustomEvent(type,{detail,bubbles:!0,composed:!0}))}refusal(error,channelRequest=!0){let refused=error instanceof ApiError2&&(error.code==="origin_not_allowed"||error.code==="page_origin"||channelRequest&&error.status===404);if(refused&&!this.refused)this.refused=!0,console.warn(`Yuva: channel ${this.channel} is unknown or does not allow ${this.pageUrl}`),this.root.replaceChildren();return refused}errorText(error){let code=error instanceof ApiError2?error.code:"";if(code==="rate_limited")return this.t({id:"tzsBOP"});if(code==="network")return this.t({id:"GQVIIl"});if(code==="anonymous_not_allowed"||code==="invalid_identity_token")return this.t({id:"uuIskV"});return this.t({id:"tAL5wg"})}frame(css,...children){let style=el("style");style.textContent=baseStyles+css;let wrapper=el("div","frame");if(wrapper.dir=this.direction,wrapper.lang=this.locale,this.#accentColor)wrapper.style.setProperty("--yuva-accent",this.#accentColor),wrapper.style.setProperty("--yuva-on-accent",contrastOn(this.#accentColor));wrapper.append(...children);let focused=null;try{focused=this.root.activeElement}catch{}if(this.root.replaceChildren(style,wrapper),focused instanceof HTMLElement&&focused.isConnected)focused.focus({preventScroll:!0})}async#identityToken(){return this.#identity?await this.#identity()??null:this.getAttribute("identity-token")}#syncPage(){if(!this.#connected)return;let page=pageOf(this.getAttribute("page"));if(page===this.pageUrl)return;this.pageUrl=page,this.reset(),this.render(),this.pageChanged()}#applyLocale(){let locale=this.locale;this.#messages=catalogs[locale]??messages,this.direction=this.getAttribute("dir")==="rtl"?"rtl":this.getAttribute("dir")==="ltr"?"ltr":directionOf(locale)}#loadSettings(){let channel=this.channel,key=`${this.server} ${channel}`;if(!channel||this.#settingsFor===key)return;this.#settingsFor=key;let stored=readStored(channel).launcher;if(stored?.color)this.#accent(stored.color);let request=settings.get(key);if(!request)request=this.client().channelSettings(),settings.set(key,request),request.catch(()=>settings.delete(key));request.then((inbox)=>{if(this.channel!==channel)return;this.inbox=inbox;let launcher=launcherOf(inbox);if(launcher.color)this.#accent(launcher.color);if(writeStored(channel,{...readStored(channel),launcher}),!this.refused)this.render()}).catch((error)=>{if(this.channel===channel)this.refusal(error)})}#accent(color){if(!/^#[0-9a-f]{6}$/i.test(color))return;this.#accentColor=color}}var hashPrefix="#yuva-discussion-",cacheFor=60000,loads=new Map;function forget(prefix){for(let key of loads.keys())if(key.startsWith(prefix))loads.delete(key)}var svg=(path)=>`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${path}</svg>`,icons={up:svg('<path d="M12 19V5M5 12l7-7 7 7"/>'),comment:svg('<path d="M21 12a8 8 0 01-11.6 7.1L4 20l1-4.6A8 8 0 1121 12z"/>'),check:svg('<path d="M20 6L9 17l-5-5"/>'),lock:svg('<rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 018 0v4"/>'),clock:svg('<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>')},styles=`
50
+ .frame { display: flex; flex-direction: column; gap: 16px; }
51
+ :host { --yuva-success: #1a7f37; --yuva-warn: #9a6700; }
52
+ @media (prefers-color-scheme: dark) { :host { --yuva-success: #3fb950; --yuva-warn: #d29922; } }
53
+ h2 { margin: 0; font-size: 1.2em; font-weight: 600; }
54
+ h3 { margin: 0; font-size: 1.15em; font-weight: 600; overflow-wrap: anywhere; }
55
+ h3:focus { outline: none; }
56
+ button { font: inherit; color: inherit; }
57
+ .head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; }
58
+ .who { display: flex; align-items: center; gap: 8px; font-size: 0.9em; }
59
+ .bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
60
+ .tabs { display: inline-flex; padding: 3px; gap: 2px; border: 1px solid var(--yuva-border); border-radius: 10px; }
61
+ .tab { padding: 4px 12px; border: 0; border-radius: 7px; background: none; cursor: pointer; opacity: 0.8; }
62
+ .tab[aria-selected="true"] { background: var(--yuva-soft); opacity: 1; font-weight: 600; }
63
+ .tab:focus-visible, .ghost:focus-visible, .vote:focus-visible, .title:focus-visible, .chip:focus-visible { outline: 2px solid var(--yuva-accent); outline-offset: 2px; }
64
+ .ghost {
65
+ display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px;
66
+ border: 1px solid var(--yuva-border); border-radius: 8px; background: none; cursor: pointer;
67
+ }
68
+ .ghost:hover:not(:disabled) { background: var(--yuva-soft); }
69
+ .ghost:disabled, .vote:disabled { opacity: 0.55; cursor: default; }
70
+ .ghost[aria-pressed="true"] { border-color: var(--yuva-accent); color: var(--yuva-accent); }
71
+ .plain { padding: 2px 4px; border: 0; background: none; cursor: pointer; font-size: 0.875em; opacity: 0.75; border-radius: 6px; }
72
+ .plain:hover:not(:disabled) { opacity: 1; background: var(--yuva-soft); }
73
+ .plain:focus-visible { outline: 2px solid var(--yuva-accent); opacity: 1; }
74
+ .plain.danger { color: var(--yuva-danger); opacity: 1; }
75
+ .list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--yuva-border); border-radius: 12px; overflow: hidden; }
76
+ .item { display: flex; gap: 12px; padding: 12px 14px; align-items: flex-start; }
77
+ .item + .item { border-block-start: 1px solid var(--yuva-border); }
78
+ .item:hover { background: var(--yuva-soft); }
79
+ .main { flex: 1; min-inline-size: 0; display: flex; flex-direction: column; gap: 2px; }
80
+ .title { padding: 0; border: 0; background: none; cursor: pointer; text-align: start; font-weight: 600; font-size: 1em; overflow-wrap: anywhere; }
81
+ .title:hover { color: var(--yuva-accent); text-decoration: underline; }
82
+ .excerpt { opacity: 0.75; font-size: 0.9em; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow-wrap: anywhere; }
83
+ .meta { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; font-size: 0.82em; opacity: 0.8; }
84
+ .counts { display: flex; gap: 12px; font-size: 0.85em; opacity: 0.8; flex-shrink: 0; padding-block-start: 2px; }
85
+ .count { display: inline-flex; align-items: center; gap: 4px; }
86
+ .avatar {
87
+ flex-shrink: 0; inline-size: 32px; block-size: 32px; border-radius: 50%;
88
+ display: inline-grid; place-items: center; font-size: 0.8em; font-weight: 600; color: #fff;
89
+ background: var(--avatar, #6b7280); text-transform: uppercase; user-select: none;
90
+ }
91
+ .avatar.small { inline-size: 24px; block-size: 24px; font-size: 0.68em; }
92
+ .avatar.team { background: var(--yuva-accent); color: var(--yuva-on-accent); }
93
+ .avatar.gone { background: var(--yuva-soft); color: inherit; }
94
+ .badge {
95
+ display: inline-flex; align-items: center; gap: 4px; padding: 1px 8px; border-radius: 999px;
96
+ font-size: 0.78em; font-weight: 600; border: 1px solid currentColor; line-height: 1.5; white-space: nowrap;
97
+ }
98
+ .badge.answered { color: var(--yuva-success); }
99
+ .badge.pending { color: var(--yuva-warn); }
100
+ .badge.team { color: var(--yuva-accent); }
101
+ .badge.locked { opacity: 0.75; }
102
+ .empty { padding: 28px 16px; text-align: center; border: 1px dashed var(--yuva-border); border-radius: 12px; }
103
+ .empty p + p { margin-block-start: 4px; }
104
+ .notice { padding: 10px 12px; border-radius: 10px; background: var(--yuva-soft); font-size: 0.9em; display: flex; gap: 8px; align-items: flex-start; }
105
+ .notice svg { flex-shrink: 0; margin-block-start: 3px; }
106
+ .more { align-self: center; }
107
+ .skeleton { block-size: 64px; border-radius: 12px; background: var(--yuva-soft); }
108
+ @media (max-width: 560px) {
109
+ .item { gap: 10px; padding: 12px; }
110
+ .item .avatar { display: none; }
111
+ .bar .send { inline-size: 100%; }
112
+ }
113
+ `;function hue(text){let hash=0;for(let char of text)hash=hash*31+char.charCodeAt(0)|0;return Math.abs(hash)%360}function excerptOf(html){let template=document.createElement("template");return template.innerHTML=html,(template.content.textContent??"").replace(/\s+/g," ").trim().slice(0,240)}class YuvaDiscussionsElement extends YuvaDocsElement{static loadThread;#s={view:{kind:"list"},items:[],thread:null,threadState:"loading",writer:null,reader:null,busy:new Set,notice:"",error:"",confirm:"",focus:""};#sort="new";#cursor;#listState="loading";#loadedFor="";#offReader=null;#thread=null;#threadLoad=null;#threadStyles="";get discussions(){return this.#s.items}get reader(){return this.#s.reader}#hashChanged=()=>this.#fromHash();connectedCallback(){super.connectedCallback(),window.addEventListener("hashchange",this.#hashChanged),this.#watchReader()}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("hashchange",this.#hashChanged),this.#offReader?.(),this.#offReader=null}attributeChangedCallback(name,previous,value){if(super.attributeChangedCallback(name,previous,value),previous===value||!this.isConnected)return;if(name==="channel"||name==="server")this.#offReader?.(),this.#offReader=null,this.#watchReader();else if(name==="identity-token")this.#refreshWriter(!0)}setIdentityToken(source){super.setIdentityToken(source),this.#refreshWriter(!0)}#watchReader(){if(!this.channel||this.#offReader)return;let client=this.client();this.#s.reader=client.reader,this.#offReader=client.on("reader",(reader)=>{this.#s.reader=reader,this.emit("yuva-reader",reader?reader.reader:null),this.#refreshWriter(!0)}),this.#refreshWriter(!1)}async#refreshWriter(reload){if(!this.channel)return;let writer=await this.client().discussionWriter().catch(()=>null),changed=writer!==this.#s.writer;if(this.#s.writer=writer,reload&&changed){if(this.#loadedFor="",this.#load(!1),this.#s.view.kind==="thread")this.#open(this.#s.view.id,!1)}this.render()}reset(){let s=this.#s;s.view={kind:"list"},s.items=[],this.#cursor=void 0,this.#listState="loading",this.#loadedFor="",s.thread=null,s.notice="",s.error="",s.confirm="",this.#thread?.reset()}pageChanged(){this.#load(!1),this.#fromHash()}#linked(){let hash=location.hash;return hash.startsWith(hashPrefix)?decodeURIComponent(hash.slice(hashPrefix.length)):""}#fromHash(){let id=this.#linked();if(!id||!this.channel||this.refused)return;if(this.#s.view.kind==="thread"&&this.#s.view.id===id)return;this.#thread?.close(),this.#s.confirm="",this.#open(id,!0),this.scrollIntoView({block:"start"})}#prefetchOn(button){let prefetch=()=>void this.#loadThread();button.addEventListener("pointerenter",prefetch,{once:!0}),button.addEventListener("focus",prefetch,{once:!0})}#loadThread(){return this.#threadLoad??=YuvaDiscussionsElement.loadThread().then((module)=>{if(this.#threadStyles=module.styles,this.#thread=module.createThread(this.#host),this.#s.view.kind!=="list")this.render();return this.#thread},()=>{if(this.#threadLoad=null,this.#s.view.kind!=="list")this.#s.view={kind:"list"},this.#s.error=this.t({id:"D16wQE"}),this.render();return null}),this.#threadLoad}#host=this.#makeHost();#makeHost(){return{s:this.#s,icons,root:this.root,locale:()=>this.locale,direction:()=>this.direction,page:()=>this.pageUrl,pageTitle:()=>this.pageTitle,mode:()=>this.#mode,client:()=>this.client(),render:()=>this.render(),emit:(type,detail)=>this.emit(type,detail),act:(key,action)=>this.#act(key,action),signIn:()=>this.signIn(),open:(id,focus)=>this.#open(id,focus),toList:()=>this.#toList(),forget:()=>forget(this.#prefix),button:(className,text,key,onClick,html)=>this.#button(className,text,key,onClick,html),avatar:(author,small)=>this.#avatar(author,small),badge:(kind,short)=>this.#badge(kind,short),time:(value)=>this.#time(value),authorName:(author)=>this.#authorName(author),noticeBox:(text,icon)=>this.#noticeBox(text,icon)}}reload(){return this.#load(!0)}open(id){return this.#open(id,!0)}signIn(){return this.#s.error="",this.client().readerSignIn().then(()=>{return}).catch((error)=>{let code=error instanceof ApiError2?error.code:"";if(code==="sign_in_cancelled")return;this.#s.error=code==="popup_blocked"?this.t({id:"nW3go1"}):this.#failure(error),this.render()})}signOut(){return this.client().readerSignOut()}get#prefix(){return`${this.server} ${this.channel} ${this.pageUrl} `}async#load(force,more=!1){let page=this.pageUrl;if(!this.channel||!page||this.refused)return;let reader=this.client().reader,auth=reader?reader.token.slice(-12):this.#s.writer??"-",key=`${this.#prefix}${this.#sort} ${auth}`;if(!force&&!more&&this.#loadedFor===key)return;this.#loadedFor=key;let request;if(more)request=this.client().listDiscussions(page,{sort:this.#sort,cursor:this.#cursor});else{let load=loads.get(key);if(force||!load||Date.now()-load.at>cacheFor){load={at:Date.now(),request:this.client().listDiscussions(page,{sort:this.#sort})},loads.set(key,load);let current=load;load.request.catch(()=>loads.get(key)===current&&loads.delete(key))}if(request=load.request,!this.#s.items.length)this.#listState="loading"}if(more)this.#s.busy.add("more");this.render();try{let result=await request;if(this.#loadedFor!==key)return;this.#s.items=more?[...this.#s.items,...result.items.filter((d)=>!this.#s.items.some((i)=>i.id===d.id))]:result.items,this.#cursor=result.next_cursor,this.#listState="ready"}catch(error){if(this.#loadedFor!==key||this.refusal(error))return;if(!more)this.#loadedFor="";if(this.#listState=error instanceof ApiError2&&error.code==="discussions_off"?"off":more?"ready":"error",more)this.#s.error=this.#failure(error)}finally{this.#s.busy.delete("more")}this.render()}async#open(id,focus){let s=this.#s,page=this.pageUrl,known=s.thread?.discussion.id===id?s.thread:null;this.#loadThread(),s.view={kind:"thread",id};let listed=s.items.find((d)=>d.id===id);if(s.thread=known??(listed?{discussion:listed,posts:[]}:null),s.threadState=known?"ready":"loading",focus)s.notice=known?s.notice:"",s.error="",s.focus="thread-title";this.render();try{let detail=await this.client().getDiscussion(id);if(this.pageUrl!==page||s.view.kind!=="thread"||s.view.id!==id)return;let titled=!!s.thread;if(s.thread=detail,s.threadState="ready",focus&&!titled)s.focus="thread-title";s.items=s.items.map((d)=>d.id===id?detail.discussion:d)}catch(error){if(this.pageUrl!==page||s.view.kind!=="thread"||s.view.id!==id)return;if(this.refusal(error,!1))return;s.threadState=error instanceof ApiError2&&error.status===404?"missing":"error"}this.render()}#toList(){let s=this.#s;if(this.#linked()){let url=new URL(location.href);url.hash="",history.replaceState(history.state,"",url.href)}s.view={kind:"list"},this.#thread?.close(),s.confirm="",s.error="",s.focus=s.thread?`open-${s.thread.discussion.id}`:"start",this.render(),this.#load(!1)}#failure(error){let code=error instanceof ApiError2?error.code:"";if(code==="sign_in_required")return this.t({id:"LwDe8g"});if(code==="discussion_locked")return this.t({id:"KEjhwb"});if(code==="edit_window_closed")return this.t({id:"b_f4xj"});if(code==="discussions_off")return this.t({id:"tayT8P"});if(code==="forbidden")return this.t({id:"8Ekoev"});if(code==="validation_failed")return this.t({id:"Su3Vyp"});return this.errorText(error)}async#act(key,action){let s=this.#s;if(s.busy.has(key))return;s.busy.add(key),s.error="",this.render();try{return await action()}catch(error){if(!this.refusal(error,!1))s.error=this.#failure(error);if(error instanceof ApiError2&&error.code==="sign_in_required")this.#refreshWriter(!1);return}finally{s.busy.delete(key),this.render()}}get#mode(){return this.inbox?.chat.discussions??"moderated"}#button(className,text,key,onClick,html=""){let button=el("button",className);if(button.type="button",html)button.innerHTML=html;return button.append(text),button.dataset.key=key,button.addEventListener("click",onClick),button}#authorName(author){if(author.deleted)return author.kind==="member"?this.t({id:"-UxBoG"}):this.t({id:"FMWGhb"});if(author.kind==="anonymous")return this.t({id:"D9CFV5"});return author.name||(author.kind==="member"?this.t({id:"nLqMRA"}):this.t({id:"zmA6sS"}))}#avatar(author,small=!1){let avatar=el("span",`avatar${small?" small":""}`,author.deleted?"":author.initials||"?");if(avatar.setAttribute("aria-hidden","true"),author.deleted||author.kind==="anonymous")avatar.classList.add("gone");else if(author.kind==="member")avatar.classList.add("team");else avatar.style.setProperty("--avatar",`hsl(${hue(author.name||author.initials)} 45% 42%)`);return avatar}#badge(kind,short=!1){let text=kind==="answered"?this.t({id:"GqCA_t"}):kind==="pending"?short?this.t({id:"UbRKMZ"}):this.t({id:"lWv8id"}):kind==="team"?this.t({id:"KM6m8p"}):this.t({id:"G2fuEb"}),badge=el("span",`badge ${kind}`);if(kind!=="team")badge.innerHTML=kind==="answered"?icons.check:kind==="pending"?icons.clock:icons.lock;return badge.append(text),badge}#time(value){let time=el("time");time.dateTime=value;let date=new Date(value);time.title=date.toLocaleString(this.locale);let seconds=(date.getTime()-Date.now())/1000,steps=[["second",60],["minute",60],["hour",24],["day",30],["month",12],["year",1/0]],amount=seconds;for(let[unit,size]of steps){if(Math.abs(amount)<size){time.textContent=unit==="second"?this.t({id:"xOTzt5"}):new Intl.RelativeTimeFormat(this.locale,{numeric:"auto"}).format(Math.round(amount),unit);break}amount/=size}return time}#header(){let head=el("div","head");head.append(el("h2","",this.t({id:"7oGBMv"})));let who=el("div","who"),reader=this.#s.reader;if(reader){let name=reader.reader.name||this.t({id:"zmA6sS"});who.append(this.#avatar({kind:"reader",name,initials:reader.reader.initials,deleted:!1},!0)),who.append(el("span","",name)),who.append(this.#button("plain",this.t({id:"fcWrnU"}),"sign-out",()=>void this.signOut()))}else if(!this.#s.writer&&this.#mode!=="off")who.append(this.#button("ghost",this.t({id:"5lWFkC"}),"sign-in",()=>void this.signIn()));return head.append(who),head}#listView(){let nodes=[],bar=el("div","bar"),tabs=el("div","tabs");tabs.setAttribute("role","tablist"),tabs.setAttribute("aria-label",this.t({id:"78dEKp"}));let sorts=[["new",this.t({id:"isRobC"})],["top",this.t({id:"WWy8TN"})],["unanswered",this.t({id:"2lF8Zj"})]];sorts.forEach(([sort,label],index)=>{let tab=this.#button("tab",label,`sort-${sort}`,()=>this.#setSort(sort));tab.setAttribute("role","tab"),tab.setAttribute("aria-selected",String(this.#sort===sort)),tab.setAttribute("aria-controls","yuva-discussion-list"),tab.tabIndex=this.#sort===sort?0:-1,tab.addEventListener("keydown",(event)=>{let step=event.key==="ArrowRight"?1:event.key==="ArrowLeft"?-1:0;if(!step)return;event.preventDefault();let forward=this.direction==="rtl"?-step:step,next=sorts[(index+forward+sorts.length)%sorts.length];if(next)this.#setSort(next[0])}),tabs.append(tab)});let start=this.#button("send",this.t({id:"z1qf6l"}),"start",()=>{this.#loadThread(),this.#s.view={kind:"new"},this.#s.error="",this.#s.notice="",this.#s.focus="new-title",this.render()});if(this.#prefetchOn(start),bar.append(tabs,start),nodes.push(bar),this.#listState==="loading"&&!this.#s.items.length){let skeleton=el("div","skeleton");return skeleton.setAttribute("aria-busy","true"),skeleton.setAttribute("aria-label",this.t({id:"pzatAP"})),nodes.push(skeleton),nodes}if(this.#listState==="error"&&!this.#s.items.length){let box=el("div","empty");box.append(el("p","",this.t({id:"MDmQKn"})));let retry=this.#button("ghost",this.t({id:"KDw4GX"}),"retry",()=>void this.#load(!0));return retry.style.marginBlockStart="8px",box.append(retry),nodes.push(box),nodes}if(!this.#s.items.length){let box=el("div","empty");if(box.append(el("p","",this.#sort==="unanswered"?this.t({id:"twEQ41"}):this.t({id:"rpfOwz"}))),this.#sort!=="unanswered")box.append(el("p","muted",this.t({id:"b6Orhy"})));return nodes.push(box),nodes}let list=el("ul","list");list.id="yuva-discussion-list",list.setAttribute("aria-label",this.t({id:"j-3eim"})),list.setAttribute("aria-busy",String(this.#listState==="loading"));for(let discussion of this.#s.items){let item=el("li","item");item.append(this.#avatar(discussion.author));let main=el("div","main"),title=this.#button("title",discussion.title,`open-${discussion.id}`,()=>void this.#open(discussion.id,!0));title.dir="auto",this.#prefetchOn(title),main.append(title);let excerpt=discussion.deleted?"":excerptOf(discussion.body_html);if(excerpt){let text=el("p","excerpt",excerpt);text.dir="auto",main.append(text)}let meta=el("div","meta");if(meta.append(el("span","",this.#authorName(discussion.author)),this.#time(discussion.created_at)),discussion.author.kind==="member"&&!discussion.author.deleted)meta.append(this.#badge("team"));if(discussion.answer_post_id)meta.append(this.#badge("answered"));if(discussion.state==="pending")meta.append(this.#badge("pending"));if(discussion.locked)meta.append(this.#badge("locked"));main.append(meta);let counts=el("div","counts"),votes=el("span","count");votes.innerHTML=icons.up,votes.append(String(discussion.votes)),votes.setAttribute("aria-label",this.t({id:"8ntmc1",values:{0:discussion.votes}})),votes.title=votes.getAttribute("aria-label")??"";let replies=el("span","count");replies.innerHTML=icons.comment,replies.append(String(discussion.reply_count)),replies.setAttribute("aria-label",this.t({id:"Hw3P48",values:{0:discussion.reply_count}})),replies.title=replies.getAttribute("aria-label")??"",counts.append(votes,replies),item.append(main,counts),list.append(item)}if(nodes.push(list),this.#cursor){let more=this.#button("ghost more",this.#s.busy.has("more")?this.t({id:"Pwqkdw"}):this.t({id:"fMPkxb"}),"more",()=>void this.#load(!1,!0));more.disabled=this.#s.busy.has("more"),nodes.push(more)}return nodes}#setSort(sort){if(this.#sort===sort)return;this.#sort=sort,this.#s.items=[],this.#cursor=void 0,this.#listState="loading",this.#s.focus=`sort-${sort}`,this.#load(!1)}#noticeBox(text,icon=icons.clock){let box=el("div","notice");return box.setAttribute("role","status"),box.innerHTML=icon,box.append(el("p","",text)),box}render(){if(this.refused||!this.channel){this.root.replaceChildren();return}let focused="";try{focused=this.root.activeElement?.dataset?.key??""}catch{}let children=[this.#header()];if(this.#mode==="off"||this.#listState==="off")children.push(el("p","muted",this.t({id:"tayT8P"})));else if(this.#s.view.kind!=="list"){let thread=this.#thread;if(thread)children.push(...this.#s.view.kind==="thread"?thread.threadView():thread.newView());else{let skeleton=el("div","skeleton");skeleton.setAttribute("aria-busy","true"),skeleton.setAttribute("aria-label",this.t({id:"Pwqkdw"})),children.push(skeleton)}}else{if(this.#s.notice)children.push(this.#noticeBox(this.#s.notice));children.push(...this.#listView())}if(this.#s.error){let error=el("p","error",this.#s.error);error.setAttribute("role","alert"),children.push(error)}if(this.frame(styles+this.#threadStyles,...children),this.#s.view.kind!=="list"&&!this.#thread)return;this.#thread?.syncSend();let wanted=this.#s.focus;this.#s.focus="";let key=wanted||focused;if(!key)return;let active=null;try{active=this.root.activeElement}catch{}if(active instanceof HTMLElement&&active.dataset.key===key)return;[...this.root.querySelectorAll("[data-key]")].find((node)=>node.dataset.key===key)?.focus({preventScroll:!wanted})}}var maxRemembered=500,thumb=(down)=>`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"${down?' style="transform:scaleY(-1)"':""}><path d="M7 10v11H3V10zM7 10l4-8a3 3 0 013 3v4h6a2 2 0 012 2.3l-1.4 8A2 2 0 0118.6 21H7"/></svg>`,styles2=`
114
+ .frame { display: flex; flex-direction: column; gap: 12px; }
115
+ .ask { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; }
116
+ .question { font-weight: 600; }
117
+ .choices { display: flex; gap: 8px; }
118
+ .choice {
119
+ display: inline-flex;
120
+ align-items: center;
121
+ gap: 6px;
122
+ padding: 6px 12px;
123
+ border: 1px solid var(--yuva-border);
124
+ border-radius: 999px;
125
+ background: transparent;
126
+ color: inherit;
127
+ font: inherit;
128
+ cursor: pointer;
129
+ }
130
+ .choice:hover:not(:disabled), .choice[aria-pressed="true"] { border-color: var(--yuva-accent); box-shadow: inset 0 0 0 1px var(--yuva-accent); }
131
+ .choice[aria-pressed="true"] { background: var(--yuva-soft); }
132
+ .choice:disabled { cursor: default; }
133
+ .choice:focus-visible { outline: 2px solid var(--yuva-accent); outline-offset: 2px; }
134
+ `;function storageKey(channel){return`yuva:${channel}:page-ratings`}function remembered(channel){try{let raw=localStorage.getItem(storageKey(channel));return raw?JSON.parse(raw):{}}catch{return{}}}function remember(channel,page,rating){try{let ratings=remembered(channel);delete ratings[page],ratings[page]=rating;let pages=Object.keys(ratings);for(let old of pages.slice(0,Math.max(0,pages.length-maxRemembered)))delete ratings[old];localStorage.setItem(storageKey(channel),JSON.stringify(ratings))}catch{}}class YuvaPageFeedbackElement extends YuvaDocsElement{#phase="ask";#rating;#error="";#comment=el("textarea");#email=el("input");constructor(){super();this.#comment.rows=3,this.#comment.maxLength=5000,this.#comment.dir="auto",this.#comment.addEventListener("input",()=>this.#syncSend()),this.#email.type="email",this.#email.autocomplete="email"}get rating(){return this.#rating??null}reset(){this.#phase="ask",this.#error="",this.#comment.value="",this.#rating=this.channel?remembered(this.channel)[this.pageUrl]:void 0}pageChanged(){if(this.#phase==="ask"&&!this.#error)this.#rating=this.channel?remembered(this.channel)[this.pageUrl]:void 0;this.render()}async rate(rating){let channel=this.channel;if(!channel||this.refused||this.#phase==="rating"||this.#phase==="sending")return;let page=this.pageUrl,previous=remembered(channel)[page];if(this.#error="",previous===rating){this.#rating=rating,this.#phase=this.canWrite?"form":"thanks",this.render(),this.#comment.focus({preventScroll:!0});return}this.#phase="rating",this.#rating=rating,this.render();try{await this.client().ratePage({page,title:this.pageTitle||void 0,rating,previous})}catch(error){if(this.pageUrl!==page||this.refusal(error))return;this.#rating=previous,this.#phase="ask",this.#error=this.errorText(error),this.render();return}if(remember(channel,page,rating),this.emit("yuva-rating",{page,rating,previous:previous??null}),this.pageUrl!==page)return;this.#phase=this.canWrite?"form":"thanks",this.render(),this.#comment.focus({preventScroll:!0})}async#send(){let body=this.#comment.value.trim(),email=this.#email.value.trim(),rating=this.#rating;if(!body||this.#phase!=="form")return;if(email&&!this.#email.checkValidity()){this.#error=this.t({id:"E_qvzP"}),this.render();return}let page=this.pageUrl;this.#phase="sending",this.#error="",this.render();try{let{conversation}=await this.client().sendFeedback({page_url:page,page_title:this.pageTitle||void 0,rating,body,locale:this.locale,allow_email:this.identified||!!email,email:!this.identified&&email?email:void 0});if(this.emit("yuva-feedback",{page,rating:rating??null,conversation_id:conversation.id}),this.pageUrl!==page)return;this.#comment.value="",this.#phase="thanks"}catch(error){if(this.pageUrl!==page||this.refusal(error,!1))return;this.#phase="form",this.#error=this.errorText(error)}this.render()}#syncSend(){let send=this.root.querySelector(".send");if(send)send.disabled=this.#phase!=="form"||this.#comment.value.trim()===""}render(){if(this.refused||!this.channel){this.root.replaceChildren();return}let question=this.t({id:"SOvBw9"}),ask=el("div","ask");ask.setAttribute("role","group"),ask.setAttribute("aria-label",question);let choices=el("div","choices");for(let value of["up","down"]){let button=el("button","choice");button.type="button",button.innerHTML=thumb(value==="down"),button.append(el("span","",value==="up"?this.t({id:"l75CjT"}):this.t({id:"1UzENP"}))),button.setAttribute("aria-pressed",String(this.#rating===value)),button.disabled=this.#phase==="rating"||this.#phase==="sending",button.addEventListener("click",()=>void this.rate(value)),choices.append(button)}ask.append(el("p","question",question),choices);let children=[ask];if(this.#phase==="form"||this.#phase==="sending"){let form=el("form");form.noValidate=!0,form.addEventListener("submit",(event)=>{event.preventDefault(),this.#send()});let prompt=this.#rating==="down"?this.t({id:"U7H5Fm"}):this.t({id:"EmiQvf"});if(this.#comment.placeholder=prompt,this.#comment.setAttribute("aria-label",prompt),this.#comment.disabled=this.#phase==="sending",form.append(this.#comment),!this.identified)this.#email.placeholder=this.t({id:"a_qtPP"}),this.#email.setAttribute("aria-label",this.t({id:"XJURKP"})),this.#email.disabled=this.#phase==="sending",form.append(this.#email);let skip=el("button","link",this.t({id:"tA4dAO"}));skip.type="button",skip.disabled=this.#phase==="sending",skip.addEventListener("click",()=>{this.#phase="thanks",this.#error="",this.render()});let send=el("button","send",this.#phase==="sending"?this.t({id:"Sad2tK"}):this.t({id:"JlFcis"}));send.type="submit";let actions=el("div","actions");actions.append(skip,send),form.append(actions),children.push(form)}else if(this.#phase==="thanks"){let thanks=el("p","muted",this.t({id:"d6N994"}));thanks.setAttribute("role","status"),children.push(thanks)}if(this.#error){let error=el("p","error",this.#error);error.setAttribute("role","alert"),children.push(error)}this.frame(styles2,...children),this.#syncSend()}}function defineDocs(){if(typeof customElements>"u")return;if(!customElements.get("yuva-discussions"))customElements.define("yuva-discussions",YuvaDiscussionsElement);if(!customElements.get("yuva-page-feedback"))customElements.define("yuva-page-feedback",YuvaPageFeedbackElement)}YuvaDiscussionsElement.loadThread=()=>import("./thread-3ydc3xfc.js");defineDocs();export{YuvaDiscussionsElement,YuvaPageFeedbackElement,defineDocs};
@@ -0,0 +1,12 @@
1
+ class ApiError2 extends Error{status;code;constructor(status,code){super(`${status} ${code}`);this.status=status;this.code=code}}async function call(fetcher,server,path,{method="GET",json,body,headers={},token,blob}={}){if(headers={...headers},token)headers.Authorization=`Bearer ${token}`;let payload=body;if(json!==void 0)headers["Content-Type"]="application/json",payload=JSON.stringify(json);let response;try{response=await fetcher(server+path,{method,headers,body:payload,credentials:"omit"})}catch{throw new ApiError2(0,"network")}if(!response.ok){let code="error";try{code=(await response.json()).code??code}catch{}throw new ApiError2(response.status,code)}if(response.status===204)return;return blob?await response.blob():await response.json()}class Realtime{server;socketClass;handlers;#socket=null;#lastEventId=0;#attempt=0;#timer=null;#stopped=!0;constructor(server,socketClass,handlers){this.server=server;this.socketClass=socketClass;this.handlers=handlers}get connected(){return this.#socket?.readyState===1}start(){if(this.#stopped=!1,!this.#socket)this.#connect()}stop(){if(this.#stopped=!0,this.#timer)clearTimeout(this.#timer);this.#timer=null;let socket=this.#socket;this.#socket=null,socket?.close(1000)}reset(){this.#lastEventId=0}nudge(){if(this.#stopped||this.#socket)return;if(this.#timer)clearTimeout(this.#timer);this.#timer=null,this.#connect()}#connect(){let token=this.handlers.token();if(!token||this.#stopped)return;let url=new URL(`${this.server}/client/v1/realtime`,globalThis.location?.href);if(url.protocol=url.protocol==="https:"?"wss:":"ws:",this.#lastEventId>0)url.searchParams.set("last_event_id",String(this.#lastEventId));let socket;try{socket=new this.socketClass(url,["yuva",`yuva.token.${token}`])}catch{this.#schedule();return}this.#socket=socket,socket.onmessage=(event)=>{let message;try{message=JSON.parse(String(event.data))}catch{return}if(message.type==="ready")this.#attempt=0,this.#lastEventId=Math.max(this.#lastEventId,message.last_event_id);else if("id"in message&&typeof message.id==="number")this.#lastEventId=Math.max(this.#lastEventId,message.id);this.handlers.event(message)},socket.onclose=(event)=>{if(this.#socket!==socket)return;if(this.#socket=null,this.#stopped)return;if(event.code===1008){this.#lastEventId=0,this.handlers.sessionEnded();return}this.#schedule()}}#schedule(){if(this.#stopped||this.#timer)return;let attempt=this.#attempt++;this.handlers.reconnecting(attempt);let delay=Math.min(30000,1000*2**attempt)*(0.75+Math.random()*0.5);this.#timer=setTimeout(()=>{this.#timer=null,this.#connect()},delay)}}function readerConnectUrl2(server,channel,origin){let url=new URL("/account/connect",server);return url.searchParams.set("channel",channel),url.searchParams.set("origin",origin),url.href}function isReaderToken(value){let data=value;return!!data&&typeof data.token==="string"&&data.token.startsWith("yuva_rd_")&&typeof data.expires_at==="string"&&!Number.isNaN(Date.parse(data.expires_at))&&typeof data.channel_key==="string"&&typeof data.origin==="string"&&!!data.reader&&typeof data.reader.initials==="string"}function openReaderPopup2(server,channel,{width=460,height=640}={}){let serverOrigin=new URL(server).origin,origin=location.origin,left=Math.max(0,(window.screenX??0)+((window.outerWidth??width)-width)/2),top=Math.max(0,(window.screenY??0)+((window.outerHeight??height)-height)/2),popup=window.open(readerConnectUrl2(server,channel,origin),"yuva-reader",`popup=yes,width=${width},height=${height},left=${Math.round(left)},top=${Math.round(top)}`);if(!popup)return Promise.reject(new ApiError2(0,"popup_blocked"));return new Promise((resolve,reject)=>{let done=!1,finish=()=>{done=!0,window.removeEventListener("message",received),clearInterval(watch)},received=(event)=>{if(done||event.origin!==serverOrigin||event.source&&event.source!==popup)return;let data=event.data;if(!isReaderToken(data)||data.channel_key!==channel||data.origin!==origin)return;finish();try{popup.close()}catch{}resolve({token:data.token,expires_at:data.expires_at,reader:data.reader})},watch=setInterval(()=>{let closed=!1;try{closed=popup.closed}catch{}if(!closed)return;clearInterval(watch),setTimeout(()=>{if(done)return;finish(),reject(new ApiError2(0,"sign_in_cancelled"))},300)},500);window.addEventListener("message",received)})}function storageKey(channel){return`yuva:${channel}`}function readerStorageKey(server,channel){return`yuva:reader:${server} ${channel}`}function randomId(){if(typeof crypto.randomUUID==="function")return crypto.randomUUID();return Array.from(crypto.getRandomValues(new Uint8Array(16)),(b)=>b.toString(16).padStart(2,"0")).join("")}function jwtSubject(token){try{let part=(token.split(".")[1]??"").replace(/-/g,"+").replace(/_/g,"/"),payload=JSON.parse(atob(part));return typeof payload.sub==="string"?payload.sub:null}catch{return null}}function defaultStorage(){try{let storage=globalThis.localStorage;if(storage)return storage}catch{}let memory=new Map;return{getItem:(key)=>memory.get(key)??null,setItem:(key,value)=>void memory.set(key,value)}}function query(params){let search=new URLSearchParams;for(let[name,value]of Object.entries(params))if(value!==void 0)search.set(name,String(value));let text=search.toString();return text?`?${text}`:""}function quoted(value){return value.replace(/"/g,"%22").replace(/\r/g,"%0D").replace(/\n/g,"%0A")}function multipart(fields,files,boundary){let parts=[];for(let[name,value]of fields)parts.push(`--${boundary}\r
2
+ Content-Disposition: form-data; name="${name}"\r
3
+ \r
4
+ ${value}\r
5
+ `);for(let file of files){let filename=file.name||"file";parts.push(`--${boundary}\r
6
+ Content-Disposition: form-data; name="files"; filename="${quoted(filename)}"\r
7
+ Content-Type: ${file.type||"application/octet-stream"}\r
8
+ \r
9
+ `,file,`\r
10
+ `)}return parts.push(`--${boundary}--\r
11
+ `),new Blob(parts)}class YuvaClient2{server;channel;#fetch;#socketClass;#storage;#identity;#idempotencyKeys;#token=null;#session=null;#starting=null;#renewing=null;#realtime=null;#reader;#handlers=new Map;constructor(options){this.server=options.server.replace(/\/+$/,""),this.channel=options.channel,this.#fetch=options.fetch??((url,init)=>fetch(url,init)),this.#socketClass=options.WebSocket??globalThis.WebSocket,this.#storage=options.storage??defaultStorage(),this.#identity=options.identityToken??null,this.#idempotencyKeys=options.idempotencyKeys??typeof globalThis.document>"u"}get session(){return this.#session}get token(){return this.#token}get connected(){return this.#realtime?.connected??!1}on(type,handler){let set=this.#handlers.get(type);if(!set)this.#handlers.set(type,set=new Set);return set.add(handler),()=>void set.delete(handler)}#emit(type,value){for(let handler of this.#handlers.get(type)??[])handler(value)}stored(){try{let raw=this.#storage.getItem(storageKey(this.channel));return raw?JSON.parse(raw):{}}catch{return{}}}#store(state){try{this.#storage.setItem(storageKey(this.channel),JSON.stringify(state))}catch{}}channelSettings(){return this.#call(`/client/v1/channels/${encodeURIComponent(this.channel)}`)}ratePage(input){return this.#call(`/client/v1/channels/${encodeURIComponent(this.channel)}/page-ratings`,{method:"POST",json:input})}setIdentityToken(source){this.#identity=source,this.reset()}start(){if(this.#session)return Promise.resolve(this.#session);return this.#starting??=this.#open(!1).then((session)=>(this.#emit("session",{session,renewed:!1}),session)).finally(()=>{this.#starting=null}),this.#starting}async refresh(){let info=await this.#authed("/client/v1/session");return this.#session={...this.#session,...info},this.#emit("session",{session:this.#session,renewed:!1}),this.#session}reset(){this.#realtime?.stop(),this.#realtime?.reset(),this.#starting=null,this.#renewing=null,this.#token=null,this.#session=null}async signOut(){let token=this.#token;this.reset();let stored=this.stored();if(delete stored.session,this.#store(stored),token)await this.#call("/client/v1/session",{method:"DELETE",token}).catch(()=>{return})}async connect(){if(!this.#socketClass)throw Error("Yuva: no WebSocket in this runtime; pass options.WebSocket");if(await this.start(),this.#realtime??=new Realtime(this.server,this.#socketClass,{token:()=>this.#token,event:(message)=>{if(message.type==="ready")this.#emit("connection",{state:"open",attempt:0});this.#emit("event",message)},sessionEnded:()=>void this.#renew().catch(()=>this.#realtime?.nudge()),reconnecting:(attempt)=>{if(this.#emit("connection",{state:"reconnecting",attempt}),attempt===2)this.#authed("/client/v1/session").catch(()=>{return})}}),!this.#realtime.connected)this.#emit("connection",{state:"connecting",attempt:0});this.#realtime.start()}disconnect(){this.#realtime?.stop(),this.#emit("connection",{state:"closed",attempt:0})}reconnect(){this.#realtime?.nudge()}listConversations(page={}){return this.#authed(`/client/v1/conversations${query({...page})}`)}getConversation(id){return this.#authed(`/client/v1/conversations/${encodeURIComponent(id)}`)}startConversation(input){return this.#authed("/client/v1/conversations",this.#messageRequest(input))}listMessages(conversationId,page={}){return this.#authed(`/client/v1/conversations/${encodeURIComponent(conversationId)}/messages${query({...page})}`)}sendMessage(conversationId,input){return this.#authed(`/client/v1/conversations/${encodeURIComponent(conversationId)}/messages`,this.#messageRequest(input))}markRead(conversationId,messageId){return this.#authed(`/client/v1/conversations/${encodeURIComponent(conversationId)}/read`,{method:"POST",json:messageId?{message_id:messageId}:{}})}rate(conversationId,rating,comment){let body={rating,comment:comment?.trim()||void 0};return this.#authed(`/client/v1/conversations/${encodeURIComponent(conversationId)}/rating`,{method:"POST",json:body})}setTyping(conversationId,typing){return this.#authed(`/client/v1/conversations/${encodeURIComponent(conversationId)}/typing`,{method:"POST",json:{typing}})}setEmail(email){return this.#authed("/client/v1/contact/email",{method:"PUT",json:{email}})}attachment(id){return this.#authed(`/client/v1/attachments/${encodeURIComponent(id)}`,{blob:!0})}sendFeedback(input){let client_id=input.client_id??randomId();return this.#authed("/client/v1/feedback",{method:"POST",headers:this.#idempotency(client_id),json:{...input,client_id}})}get reader(){if(this.#reader===void 0)try{let raw=this.#storage.getItem(readerStorageKey(this.server,this.channel));this.#reader=raw?JSON.parse(raw):null}catch{this.#reader=null}if(this.#reader&&Date.parse(this.#reader.expires_at)<=Date.now()+30000)this.#setReader(null);return this.#reader}#setReader(reader){let changed=this.#reader?.token!==reader?.token;this.#reader=reader;try{this.#storage.setItem(readerStorageKey(this.server,this.channel),reader?JSON.stringify(reader):"")}catch{}if(changed)this.#emit("reader",reader)}readerSignIn(options){return openReaderPopup2(this.server,this.channel,options).then((reader)=>(this.#setReader(reader),reader))}setReader(reader){this.#setReader(reader)}async readerSignOut(){let token=this.reader?.token;if(this.#setReader(null),token)await this.#call("/client/v1/reader-token",{method:"DELETE",token}).catch(()=>{return})}listDiscussions(page,options={}){return this.#discussionCall(`/client/v1/channels/${encodeURIComponent(this.channel)}/discussions${query({page,...options})}`,{},!1)}getDiscussion(id){return this.#discussionCall(`/client/v1/channels/${encodeURIComponent(this.channel)}/discussions/${encodeURIComponent(id)}`,{},!1)}startDiscussion(input){return this.#discussionCall("/client/v1/discussions",{method:"POST",json:input})}replyToDiscussion(discussionId,input){return this.#discussionCall(`/client/v1/discussions/${encodeURIComponent(discussionId)}/posts`,{method:"POST",json:input})}editDiscussionPost(postId,input){return this.#discussionCall(`/client/v1/discussion-posts/${encodeURIComponent(postId)}`,{method:"PATCH",json:input})}deleteDiscussionPost(postId){return this.#discussionCall(`/client/v1/discussion-posts/${encodeURIComponent(postId)}`,{method:"DELETE"})}voteDiscussionPost(postId,voted=!0){return this.#discussionCall(`/client/v1/discussion-posts/${encodeURIComponent(postId)}/vote`,{method:voted?"PUT":"DELETE"})}followDiscussion(discussionId,following=!0){return this.#discussionCall(`/client/v1/discussions/${encodeURIComponent(discussionId)}/follow`,{method:following?"PUT":"DELETE"})}acceptDiscussionAnswer(discussionId,postId){let path=`/client/v1/discussions/${encodeURIComponent(discussionId)}/answer`;return this.#discussionCall(path,postId?{method:"PUT",json:{post_id:postId}}:{method:"DELETE"})}async discussionWriter(){if(this.reader)return"reader";if(this.#session)return this.#session.contact.identified?"contact":null;return await this.#identity?.()?"contact":null}async#discussionCall(path,request,write=!0){let reader=this.reader;if(reader)try{return await this.#call(path,{...request,token:reader.token})}catch(error){if(!(error instanceof ApiError2)||error.status!==401||this.#reader?.token!==reader.token)throw error;if(this.#setReader(null),write)throw new ApiError2(403,"sign_in_required")}if(await this.discussionWriter()==="contact")return this.#authed(path,request);if(write)throw new ApiError2(403,"sign_in_required");return this.#call(path,request)}#idempotency(client_id){return this.#idempotencyKeys&&/^[\x20-\x7e]{1,255}$/.test(client_id)?{"Idempotency-Key":client_id}:{}}#messageRequest({body,subject,files=[],client_id=randomId()}){let headers=this.#idempotency(client_id);if(files.length===0)return{method:"POST",headers,json:{body,subject,client_id}};let fields=[];if(subject)fields.push(["subject",subject]);if(body)fields.push(["body",body]);fields.push(["client_id",client_id]);let boundary=`yuva-${client_id.replace(/[^\w-]/g,"").slice(0,60)}-${client_id.length}`;return headers["Content-Type"]=`multipart/form-data; boundary=${boundary}`,{method:"POST",headers,body:multipart(fields,files,boundary)}}#call(path,request){return call(this.#fetch,this.server,path,request)}async#authed(path,request={},retry=!0){if(!this.#token)await this.start();try{return await this.#call(path,{...request,token:this.#token})}catch(error){if(!retry||!(error instanceof ApiError2)||error.status!==401)throw error;return await this.#renew(),this.#authed(path,request,!1)}}#renew(){return this.#renewing??=(async()=>{let session=await this.#open(!0);if(this.#realtime)this.#realtime.stop(),this.#realtime.reset(),this.#realtime.start();this.#emit("session",{session,renewed:!0})})().finally(()=>{this.#renewing=null}),this.#renewing}async#open(fresh){let stored=this.stored(),identity=await this.#identity?.()||null,subject=identity?jwtSubject(identity):null,saved=stored.session;if(!fresh&&saved&&saved.sub===subject&&Date.parse(saved.expires_at)>Date.now()+60000)try{let info=await this.#call("/client/v1/session",{token:saved.token});return this.#apply(info,saved.token,subject)}catch(error){if(!(error instanceof ApiError2)||error.status!==401&&error.status!==403)throw error}let session=await this.#call("/client/v1/session",{method:"POST",json:{channel_key:this.channel,identity_token:identity??void 0,visitor_id:stored.visitor_id}}),{token,...info}=session;return this.#apply(info,token,subject)}#apply(info,token,subject){this.#token=token,this.#session=info;let stored=this.stored();if(stored.session={token,expires_at:info.expires_at,sub:subject},info.visitor_id)stored.visitor_id=info.visitor_id;else if(info.contact.identified)delete stored.visitor_id;return this.#store(stored),info}}function createYuvaClient2(options){return new YuvaClient2(options)}
12
+ export{ApiError2,readerConnectUrl2,openReaderPopup2,randomId,YuvaClient2,createYuvaClient2};
@@ -0,0 +1,2 @@
1
+ var messages=JSON.parse('{"6I7cJR":["Open chat"],"OkHvGB":["Open chat, ",["count"]," unread"],"pqZJT2":["Close chat"]}');var messages2=JSON.parse('{"6I7cJR":["Sohbeti aç"],"OkHvGB":["Sohbeti aç, ",["count"]," okunmamış"],"pqZJT2":["Sohbeti kapat"]}');function translate(messages,descriptor,fallback){let entry=messages[descriptor.id]??fallback[descriptor.id],values=descriptor.values??{};if(typeof entry==="string")return entry;if(Array.isArray(entry))return entry.map((part)=>typeof part==="string"?part:Array.isArray(part)?String(values[part[0]]??""):"").join("");return descriptor.message??descriptor.id}var catalogs={en:messages,tr:messages2},rtlLanguages=new Set(["ar","fa","he","ur"]),sourceLocale="en";function resolveLocale(...candidates){for(let candidate of candidates){if(!candidate)continue;let tag=candidate.toLowerCase();if(catalogs[tag])return tag;let language=tag.split(/[-_]/)[0]??"";if(catalogs[language])return language}return sourceLocale}function directionOf(locale){return rtlLanguages.has(locale.split(/[-_]/)[0]??"")?"rtl":"ltr"}function catalogFor(locale){return catalogs[locale]??messages}function translatePlain(messages2,descriptor,fallback=messages){return translate(messages2,descriptor,fallback)}function launcherOf(inbox){return{position:inbox.chat.launcher_position,color:inbox.chat.launcher_color??inbox.branding.color}}function contrastOn(color){let n=Number.parseInt(color.slice(1),16),[r,g,b]=[n>>16&255,n>>8&255,n&255].map((c)=>{let s=c/255;return s<=0.03928?s/12.92:((s+0.055)/1.055)**2.4});return 0.2126*r+0.7152*g+0.0722*b>0.4?"#111827":"#ffffff"}function storageKey(channel){return`yuva:${channel}`}function readStored(channel){try{let raw=localStorage.getItem(storageKey(channel));return raw?JSON.parse(raw):{}}catch{return{}}}function writeStored(channel,state){try{localStorage.setItem(storageKey(channel),JSON.stringify(state))}catch{}}
2
+ export{translate,resolveLocale,directionOf,catalogFor,translatePlain,launcherOf,contrastOn,readStored,writeStored};
@@ -0,0 +1,4 @@
1
+ var inline=/(`[^`\n]+`)|(\*\*[^*\n]+\*\*)|(\*[^*\s][^*\n]*\*|_[^_\s][^_\n]*_)|((?:https?:\/\/|www\.)[^\s<>"]*[^\s<>".,:;'!?)\]])/g;function link(href,text){let a=document.createElement("a");return a.href=href,a.textContent=text,a.target="_blank",a.rel="noopener noreferrer nofollow ugc",a}function wrap(tag,children){let element=document.createElement(tag);return element.append(...children),element}function inlineNodes(text){let nodes=[],last=0;for(let match of text.matchAll(inline)){let index=match.index??0;if(index>last)nodes.push(document.createTextNode(text.slice(last,index)));let[whole,code,bold,italic,url]=match;if(code)nodes.push(wrap("code",[document.createTextNode(code.slice(1,-1))]));else if(bold)nodes.push(wrap("strong",inlineNodes(bold.slice(2,-2))));else if(italic)nodes.push(wrap("em",inlineNodes(italic.slice(1,-1))));else if(url){let href=url.startsWith("www.")?`https://${url}`:url;try{let parsed=new URL(href);nodes.push(parsed.protocol==="http:"||parsed.protocol==="https:"?link(parsed.href,url):document.createTextNode(url))}catch{nodes.push(document.createTextNode(url))}}last=index+whole.length}if(last<text.length)nodes.push(document.createTextNode(text.slice(last)));return nodes}function richText(body){let fragment=document.createDocumentFragment(),parts=body.trim().replace(/\r\n?/g,`
2
+ `).split(/^```[^\n]*\n([\s\S]*?)^```[ \t]*$/m);return parts.forEach((part,i)=>{if(i%2===1){let pre=document.createElement("pre");pre.textContent=part.replace(/\n$/,""),fragment.append(pre);return}(parts.length>1?part.replace(/^\n|\n$/g,""):part).split(`
3
+ `).forEach((line,n)=>{if(n>0)fragment.append(document.createElement("br"));fragment.append(...inlineNodes(line))})}),fragment}
4
+ export{inlineNodes,richText};
@@ -0,0 +1,2 @@
1
+ function el(tag,className,text){let element=document.createElement(tag);if(className)element.className=className;if(text!==void 0)element.textContent=text;return element}
2
+ export{el};
package/dist/esm/index.js CHANGED
@@ -1 +1 @@
1
- import{ApiError2,YuvaClient2,createYuvaClient2}from"./index-d1vxbqpr.js";export{ApiError2 as ApiError,YuvaClient2 as YuvaClient,createYuvaClient2 as createYuvaClient};
1
+ import{ApiError2,readerConnectUrl2,openReaderPopup2,YuvaClient2,createYuvaClient2}from"./index-dj5dhqc1.js";export{ApiError2 as ApiError,YuvaClient2 as YuvaClient,createYuvaClient2 as createYuvaClient,openReaderPopup2 as openReaderPopup,readerConnectUrl2 as readerConnectUrl};
@@ -0,0 +1,20 @@
1
+ import { addComponent, defineNuxtModule } from "@nuxt/kit";
2
+ const components = ["YuvaChat", "YuvaDiscussions", "YuvaPageFeedback"];
3
+ export default defineNuxtModule({
4
+ meta: { name: "useyuva", configKey: "yuva" },
5
+ defaults: { server: "", channel: "", locale: "" },
6
+ setup(options, nuxt) {
7
+ const current = nuxt.options.runtimeConfig.public.yuva ?? {};
8
+ nuxt.options.runtimeConfig.public.yuva = {
9
+ server: current.server || options.server || "",
10
+ channel: current.channel || options.channel || "",
11
+ locale: current.locale || options.locale || ""
12
+ };
13
+ const compilerOptions = nuxt.options.vue.compilerOptions ??= {};
14
+ const isCustomElement = compilerOptions.isCustomElement;
15
+ compilerOptions.isCustomElement = (tag) => tag.startsWith("yuva-") || !!isCustomElement?.(tag);
16
+ nuxt.options.build.transpile.push("useyuva");
17
+ for (const name of components)
18
+ addComponent({ name, export: name, filePath: "useyuva/nuxt/components" });
19
+ }
20
+ });
@@ -0,0 +1,88 @@
1
+ import { defineComponent, h, onMounted, ref, watch } from "vue";
2
+ import { useRuntimeConfig } from "#imports";
3
+ const shared = {
4
+ channel: String,
5
+ server: String,
6
+ locale: String,
7
+ dir: String,
8
+ identityToken: [String, Function]
9
+ };
10
+ const docsProps = { ...shared, page: String, pageTitle: String };
11
+ const chatProps = { ...shared, layout: String, open: Boolean };
12
+ function setup(tag, load, props, attributes, listeners) {
13
+ const config = useRuntimeConfig().public.yuva;
14
+ const element = ref(null);
15
+ onMounted(() => {
16
+ load();
17
+ watch(() => props.identityToken, (source) => {
18
+ const target = element.value;
19
+ if (!target || typeof source !== "function")
20
+ return;
21
+ customElements.whenDefined(tag).then(() => target.setIdentityToken(source));
22
+ }, { immediate: true });
23
+ });
24
+ return {
25
+ element,
26
+ render: () => {
27
+ const values = {
28
+ channel: props.channel || config?.channel || undefined,
29
+ server: props.server || config?.server || undefined,
30
+ locale: props.locale || config?.locale || undefined,
31
+ "identity-token": typeof props.identityToken === "string" ? props.identityToken : undefined,
32
+ dir: props.dir,
33
+ ...attributes(props)
34
+ };
35
+ const vnode = { ref: element, ...listeners };
36
+ for (const [name, value] of Object.entries(values))
37
+ vnode[`^${name}`] = value === "" || value == null ? undefined : value;
38
+ return h(tag, vnode);
39
+ }
40
+ };
41
+ }
42
+ const detail = (emit) => (event) => emit(event.detail);
43
+ export const YuvaPageFeedback = defineComponent({
44
+ name: "YuvaPageFeedback",
45
+ props: docsProps,
46
+ emits: {
47
+ rating: (_) => true,
48
+ feedback: (_) => true
49
+ },
50
+ setup(props, { emit, expose }) {
51
+ const { element, render } = setup("yuva-page-feedback", () => import("../../docs.js"), props, (p) => ({ page: p.page, "page-title": p.pageTitle }), {
52
+ onYuvaRating: detail((d) => emit("rating", d)),
53
+ onYuvaFeedback: detail((d) => emit("feedback", d))
54
+ });
55
+ expose({ element });
56
+ return render;
57
+ }
58
+ });
59
+ export const YuvaDiscussions = defineComponent({
60
+ name: "YuvaDiscussions",
61
+ props: docsProps,
62
+ emits: {
63
+ discussion: (_) => true,
64
+ post: (_) => true,
65
+ reader: (_) => true
66
+ },
67
+ setup(props, { emit, expose }) {
68
+ const { element, render } = setup("yuva-discussions", () => import("../../docs.js"), props, (p) => ({ page: p.page, "page-title": p.pageTitle }), {
69
+ onYuvaDiscussion: detail((d) => emit("discussion", d)),
70
+ onYuvaPost: detail((d) => emit("post", d)),
71
+ onYuvaReader: detail((d) => emit("reader", d))
72
+ });
73
+ expose({ element });
74
+ return render;
75
+ }
76
+ });
77
+ export const YuvaChat = defineComponent({
78
+ name: "YuvaChat",
79
+ props: chatProps,
80
+ emits: {
81
+ unread: (_) => true
82
+ },
83
+ setup(props, { emit, expose }) {
84
+ const { element, render } = setup("yuva-chat", () => import("../../chat.js"), props, (p) => ({ layout: p.layout, open: p.open || undefined }), { onYuvaUnread: detail((d) => emit("unread", d)) });
85
+ expose({ element });
86
+ return render;
87
+ }
88
+ });
package/dist/esm/react.js CHANGED
@@ -1 +1 @@
1
- import{createContext,createElement,useCallback,useContext,useEffect,useRef,useState}from"react";var YuvaContext=createContext(null);function YuvaProvider({client,connect=!0,children}){return useEffect(()=>{if(!connect)return;return client.connect().catch(()=>{return}),()=>client.disconnect()},[client,connect]),createElement(YuvaContext.Provider,{value:client},children)}function useYuva(){let client=useContext(YuvaContext);if(!client)throw Error("Yuva: wrap your components in <YuvaProvider client={...}>");return client}var time=(value)=>Date.parse(value),byCreated=(a,b)=>time(a.created_at)-time(b.created_at),byActivity=(a,b)=>time(b.last_message_at??b.created_at)-time(a.last_message_at??a.created_at),initial={items:[],loading:!0,error:null};function useConversations(){let client=useYuva(),[state,setState]=useState(initial),cursor=useRef(void 0),current=useRef(state);current.current=state;let load=useCallback(async(more)=>{setState((s)=>({...s,loading:!0}));try{let page=await client.listConversations({limit:50,cursor:more?cursor.current:void 0});cursor.current=page.next_cursor,setState((s)=>{let items=new Map((more?s.items:[]).map((c)=>[c.id,c]));for(let conversation of page.items)items.set(conversation.id,conversation);return{items:[...items.values()].sort(byActivity),cursor:page.next_cursor,loading:!1,error:null}})}catch(error){setState((s)=>({...s,loading:!1,error}))}},[client]);return useEffect(()=>{load(!1);let offSession=client.on("session",({renewed})=>void(renewed&&load(!1))),offEvent=client.on("event",(message)=>{if(message.type==="resync_required")return void load(!1);if(message.type==="conversation.created")setState((s)=>s.items.some((c)=>c.id===message.data.id)?s:{...s,items:[message.data,...s.items].sort(byActivity)});else if(message.type==="conversation.updated")setState((s)=>({...s,items:s.items.map((c)=>c.id===message.conversation_id?{...c,status:message.data.status}:c)}));else if(message.type==="message.created"){let m=message.data;if(!current.current.items.some((c)=>c.id===m.conversation_id))return void load(!1);setState((s)=>{let items=s.items.map((c)=>c.id===m.conversation_id?{...c,last_message:{id:m.id,author_type:m.author.type,text:m.body.slice(0,140),created_at:m.created_at},last_message_at:m.created_at,unread:c.unread||m.author.type!=="contact"}:c);return{...s,items:items.sort(byActivity)}})}});return()=>{offSession(),offEvent()}},[client,load]),{conversations:state.items,loading:state.loading,error:state.error,hasMore:!!state.cursor,loadMore:()=>load(!0),reload:()=>load(!1)}}function useMessages(conversationId){let client=useYuva(),[state,setState]=useState(initial),cursor=useRef(void 0),upsert=useCallback((message)=>{setState((s)=>{let items=s.items.filter((m)=>m.id!==message.id&&(!message.client_id||m.client_id!==message.client_id));return{...s,items:[...items,message].sort(byCreated)}})},[]),load=useCallback(async(older)=>{if(!conversationId){cursor.current=void 0,setState({items:[],loading:!1,error:null});return}if(older&&!cursor.current)return;setState((s)=>({...s,loading:!0}));try{let page=await client.listMessages(conversationId,{order:"desc",limit:30,cursor:older?cursor.current:void 0});cursor.current=page.next_cursor,setState((s)=>{let items=new Map((older?s.items:[]).map((m)=>[m.id,m]));for(let message of page.items)items.set(message.id,message);return{items:[...items.values()].sort(byCreated),cursor:page.next_cursor,loading:!1,error:null}})}catch(error){setState((s)=>({...s,loading:!1,error}))}},[client,conversationId]);return useEffect(()=>{if(load(!1),!conversationId)return;let offSession=client.on("session",({renewed})=>void(renewed&&load(!1))),offEvent=client.on("event",(message)=>{if(message.type==="resync_required")load(!1);else if((message.type==="message.created"||message.type==="message.updated")&&message.data.conversation_id===conversationId)upsert(message.data)});return()=>{offSession(),offEvent()}},[client,conversationId,load,upsert]),{messages:state.items,loading:state.loading,error:state.error,hasMore:!!state.cursor,loadOlder:()=>load(!0),async send(input){if(!conversationId)throw Error("Yuva: no conversation; start one with client.startConversation()");let message=await client.sendMessage(conversationId,typeof input==="string"?{body:input}:input);return upsert(message),message},async markRead(){if(conversationId)await client.markRead(conversationId)}}}export{YuvaProvider,useConversations,useMessages,useYuva};
1
+ import{createContext,createElement,useCallback,useContext,useEffect,useRef,useState}from"react";var YuvaContext=createContext(null);function YuvaProvider({client,connect=!0,children}){return useEffect(()=>{if(!connect)return;return client.connect().catch(()=>{return}),()=>client.disconnect()},[client,connect]),createElement(YuvaContext.Provider,{value:client},children)}function useYuva(){let client=useContext(YuvaContext);if(!client)throw Error("Yuva: wrap your components in <YuvaProvider client={...}>");return client}var time=(value)=>Date.parse(value),byCreated=(a,b)=>time(a.created_at)-time(b.created_at),byActivity=(a,b)=>time(b.last_message_at??b.created_at)-time(a.last_message_at??a.created_at),initial={items:[],loading:!0,error:null};function useConversations(){let client=useYuva(),[state,setState]=useState(initial),cursor=useRef(void 0),current=useRef(state);useEffect(()=>{current.current=state});let load=useCallback(async(more)=>{setState((s)=>({...s,loading:!0}));try{let page=await client.listConversations({limit:50,cursor:more?cursor.current:void 0});cursor.current=page.next_cursor,setState((s)=>{let items=new Map((more?s.items:[]).map((c)=>[c.id,c]));for(let conversation of page.items)items.set(conversation.id,conversation);return{items:[...items.values()].sort(byActivity),cursor:page.next_cursor,loading:!1,error:null}})}catch(error){setState((s)=>({...s,loading:!1,error}))}},[client]);return useEffect(()=>{load(!1);let offSession=client.on("session",({renewed})=>void(renewed&&load(!1))),offEvent=client.on("event",(message)=>{if(message.type==="resync_required")return void load(!1);if(message.type==="conversation.created")setState((s)=>s.items.some((c)=>c.id===message.data.id)?s:{...s,items:[message.data,...s.items].sort(byActivity)});else if(message.type==="conversation.updated")setState((s)=>({...s,items:s.items.map((c)=>c.id===message.conversation_id?{...c,status:message.data.status}:c)}));else if(message.type==="message.created"){let m=message.data;if(!current.current.items.some((c)=>c.id===m.conversation_id))return void load(!1);setState((s)=>{let items=s.items.map((c)=>c.id===m.conversation_id?{...c,last_message:{id:m.id,author_type:m.author.type,text:m.body.slice(0,140),created_at:m.created_at},last_message_at:m.created_at,unread:c.unread||m.author.type!=="contact"}:c);return{...s,items:items.sort(byActivity)}})}});return()=>{offSession(),offEvent()}},[client,load]),{conversations:state.items,loading:state.loading,error:state.error,hasMore:!!state.cursor,loadMore:()=>load(!0),reload:()=>load(!1)}}function useMessages(conversationId){let client=useYuva(),[state,setState]=useState(initial),cursor=useRef(void 0),upsert=useCallback((message)=>{setState((s)=>{let items=s.items.filter((m)=>m.id!==message.id&&(!message.client_id||m.client_id!==message.client_id));return{...s,items:[...items,message].sort(byCreated)}})},[]),load=useCallback(async(older)=>{if(!conversationId){cursor.current=void 0,setState({items:[],loading:!1,error:null});return}if(older&&!cursor.current)return;setState((s)=>({...s,loading:!0}));try{let page=await client.listMessages(conversationId,{order:"desc",limit:30,cursor:older?cursor.current:void 0});cursor.current=page.next_cursor,setState((s)=>{let items=new Map((older?s.items:[]).map((m)=>[m.id,m]));for(let message of page.items)items.set(message.id,message);return{items:[...items.values()].sort(byCreated),cursor:page.next_cursor,loading:!1,error:null}})}catch(error){setState((s)=>({...s,loading:!1,error}))}},[client,conversationId]);return useEffect(()=>{if(load(!1),!conversationId)return;let offSession=client.on("session",({renewed})=>void(renewed&&load(!1))),offEvent=client.on("event",(message)=>{if(message.type==="resync_required")load(!1);else if((message.type==="message.created"||message.type==="message.updated")&&message.data.conversation_id===conversationId)upsert(message.data)});return()=>{offSession(),offEvent()}},[client,conversationId,load,upsert]),{messages:state.items,loading:state.loading,error:state.error,hasMore:!!state.cursor,loadOlder:()=>load(!0),async send(input){if(!conversationId)throw Error("Yuva: no conversation; start one with client.startConversation()");let message=await client.sendMessage(conversationId,typeof input==="string"?{body:input}:input);return upsert(message),message},async markRead(){if(conversationId)await client.markRead(conversationId)}}}function useDocsElement(tag,props,handlers){let[element,setElement]=useState(null),latest=useRef(handlers);useEffect(()=>{latest.current=handlers});let{channel,server,locale,dir,page,pageTitle,identityToken,className,style}=props;useEffect(()=>{import("./docs.js")},[]),useEffect(()=>{if(!element)return;let attributes={server,locale,dir,page,"page-title":pageTitle,"identity-token":typeof identityToken==="string"?identityToken:void 0,channel};for(let[name,value]of Object.entries(attributes))if(value)element.setAttribute(name,value);else element.removeAttribute(name)},[element,channel,server,locale,dir,page,pageTitle,identityToken]),useEffect(()=>{if(!element||typeof identityToken!=="function")return;let current=!0;return customElements.whenDefined(tag).then(()=>{if(current)element.setIdentityToken(identityToken)}),()=>{current=!1}},[element,tag,identityToken]);let events=Object.keys(handlers).join(" ");return useEffect(()=>{if(!element)return;let listeners=events.split(" ").map((type)=>{let listener=(event)=>latest.current[type]?.(event.detail);return element.addEventListener(type,listener),()=>element.removeEventListener(type,listener)});return()=>listeners.forEach((off)=>off())},[element,events]),createElement(tag,{ref:setElement,className,style})}function YuvaPageFeedback({onRating,onFeedback,...props}){return useDocsElement("yuva-page-feedback",props,{"yuva-rating":onRating,"yuva-feedback":onFeedback})}function YuvaDiscussions({onDiscussion,onPost,onReader,...props}){return useDocsElement("yuva-discussions",props,{"yuva-discussion":onDiscussion,"yuva-post":onPost,"yuva-reader":onReader})}export{YuvaDiscussions,YuvaPageFeedback,YuvaProvider,useConversations,useMessages,useYuva};