@momentco-ai/moment-sdk 0.3.2 → 0.4.0-dev.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -20
- package/dist/moment-sdk.js +2 -2
- package/dist/moment-sdk.mjs +84 -71
- package/dist/types/sdk/types.d.ts +49 -4
- package/examples/README.md +1 -0
- package/examples/index.html +4 -0
- package/examples/schedule/index.html +43 -0
- package/examples/schedule/main.js +23 -0
- package/examples/serve.mjs +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,14 +91,16 @@ Clicking the button opens a modal where the fan selects Google Calendar or Outlo
|
|
|
91
91
|
|
|
92
92
|
Set `data-moment-trigger-type` (or `triggerType` in `open()` / `createMomentButton()`):
|
|
93
93
|
|
|
94
|
-
| Type | Required attributes
|
|
95
|
-
| ------------------ |
|
|
96
|
-
| `moment` (default) | `data-moment-team-slug` + `data-moment-slug`
|
|
97
|
-
| `list` | `data-moment-team-slug` + `data-moment-list-slug`
|
|
98
|
-
| `team` | `data-moment-team-slug`
|
|
99
|
-
| `brand-playlist` | `data-moment-team-slug`
|
|
94
|
+
| Type | Required attributes | Subscribes to |
|
|
95
|
+
| ------------------ | ------------------------------------------------------ | ----------------------------------------------------------------- |
|
|
96
|
+
| `moment` (default) | `data-moment-team-slug` + `data-moment-slug` | One or more specific games/events |
|
|
97
|
+
| `list` | `data-moment-team-slug` + `data-moment-list-slug` | A curated list of moments |
|
|
98
|
+
| `team` | `data-moment-team-slug` | All published moments for the brand/team |
|
|
99
|
+
| `brand-playlist` | `data-moment-team-slug` | The brand's default playlist (dynamic schedule thread) |
|
|
100
|
+
| `playlist` | `data-moment-team-slug` + `data-moment-playlist-slugs` | One of several playlists (picker when >1 slug) |
|
|
101
|
+
| `schedule` | `data-moment-team-slug` | Full schedule experience — user picks a mix of playlists + events |
|
|
100
102
|
|
|
101
|
-
For `team
|
|
103
|
+
For `team`, `brand-playlist`, and `schedule`, omit moment/list/ID and source-resolution attributes — only `data-moment-team-slug` is required.
|
|
102
104
|
|
|
103
105
|
---
|
|
104
106
|
|
|
@@ -106,18 +108,19 @@ For `team` and `brand-playlist`, omit moment/list/ID and source-resolution attri
|
|
|
106
108
|
|
|
107
109
|
Place these on any element matching the trigger selector (default: `.moment-sync-trigger`):
|
|
108
110
|
|
|
109
|
-
| Attribute | Required
|
|
110
|
-
| ------------------------------- |
|
|
111
|
-
| `data-moment-team-slug` | Yes
|
|
112
|
-
| `data-moment-slug` | For `moment`
|
|
113
|
-
| `data-moment-list-slug` | For `list`
|
|
114
|
-
| `data-moment-trigger-type` | No
|
|
115
|
-
| `data-moment-
|
|
116
|
-
| `data-moment-
|
|
117
|
-
| `data-moment-
|
|
118
|
-
| `data-
|
|
119
|
-
| `data-
|
|
120
|
-
| `data-external-event-id
|
|
111
|
+
| Attribute | Required | Description |
|
|
112
|
+
| ------------------------------- | -------------- | ------------------------------------------------------------------------------- |
|
|
113
|
+
| `data-moment-team-slug` | Yes | Team/brand slug provided by Moment |
|
|
114
|
+
| `data-moment-slug` | For `moment` | Brand-level moment slug (resolved server-side) |
|
|
115
|
+
| `data-moment-list-slug` | For `list` | List slug |
|
|
116
|
+
| `data-moment-trigger-type` | No | `moment` (default), `list`, `team`, `brand-playlist`, `playlist`, or `schedule` |
|
|
117
|
+
| `data-moment-playlist-slugs` | For `playlist` | Comma-separated playlist slugs (picker shown when more than one) |
|
|
118
|
+
| `data-moment-ids` | No | Comma-separated moment IDs (fallback if slugs don't resolve) |
|
|
119
|
+
| `data-moment-game-id` | No | Alias for `data-moment-ids` |
|
|
120
|
+
| `data-moment-calendar` | No | Pre-select provider: `google` or `outlook` |
|
|
121
|
+
| `data-api-source-id` | No | Sync-subscription source ID (pairs with external id attributes) |
|
|
122
|
+
| `data-external-event-id` | No | Exact external id (`event_id:showing_id`) resolved against source |
|
|
123
|
+
| `data-external-event-id-prefix` | No | Upstream event id only — user picks a showing in the embed modal |
|
|
121
124
|
|
|
122
125
|
When `data-api-source-id` is set with either `data-external-event-id` (one showing) or `data-external-event-id-prefix` (all showings for an event), Live resolves moments server-side — useful when your CMS emits native event IDs instead of Moment slugs.
|
|
123
126
|
|
|
@@ -273,7 +276,7 @@ The `onAnalytics` callback receives:
|
|
|
273
276
|
}
|
|
274
277
|
```
|
|
275
278
|
|
|
276
|
-
`result` is one of `success`, `error`, or `
|
|
279
|
+
`result` is one of `success`, `error`, `cancelled`, or `partial` (schedule trigger, when only some resources synced). `provider` is `google`, `outlook`, or `apple` when applicable. `triggerType` echoes the trigger (`moment`, `list`, `team`, `brand-playlist`, `playlist`, or `schedule`). For `schedule`, an optional `counts: { requested, succeeded, failed }` reports the aggregate outcome.
|
|
277
280
|
|
|
278
281
|
---
|
|
279
282
|
|
package/dist/moment-sdk.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(v){"use strict";class S{popup=null;pollTimer=null;messageHandler=null;resultReceived=!1;opts;constructor(e){this.opts=e}open(e){this.cleanup(),this.resultReceived=!1;const t=this.buildPopupUrl(e),i=Math.round(window.screenX+(window.outerWidth-500)/2),o=Math.round(window.screenY+(window.outerHeight-700)/2);if(this.popup=window.open(t,"moment-oauth-popup",`width=500,height=700,left=${i},top=${o},menubar=no,toolbar=no,location=yes,status=no`),!this.popup||this.popup.closed){this.sendStatusToIframe("popup-blocked"),this.opts.onPopupBlocked();return}this.sendStatusToIframe("popup-opened"),this.startPolling(),this.messageHandler=n=>{if(n.origin!==this.opts.liveOrigin||n.source!==this.popup)return;const r=n.data;!r||typeof r!="object"||r.source!=="moment-live-embed"||r.type!=="moment.embed.result"||(this.resultReceived=!0,this.sendStatusToIframe("completed"),this.opts.iframeWindow&&this.opts.iframeWindow.postMessage(r,this.opts.liveOrigin),this.opts.onResult(r),this.cleanup())},window.addEventListener("message",this.messageHandler)}cleanup(){if(this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.popup&&!this.popup.closed)try{this.popup.close()}catch{}this.popup=null}buildPopupUrl(e){const t=window.location.origin,i=new URLSearchParams({provider:e.provider,teamSlug:e.teamSlug,ids:e.ids.join(","),triggerType:e.triggerType,returnOrigin:t});return e.subscriptionType&&i.set("subscriptionType",e.subscriptionType),e.scheduleMomentIds?.length&&i.set("scheduleMomentIds",e.scheduleMomentIds.join(",")),e.schedulePlaylistIds?.length&&i.set("schedulePlaylistIds",e.schedulePlaylistIds.join(",")),`${this.opts.liveBaseUrl}/en/embed/oauth-popup?${i.toString()}`}startPolling(){this.pollTimer=setInterval(()=>{(!this.popup||this.popup.closed)&&(this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.resultReceived||(this.sendStatusToIframe("popup-closed"),this.opts.onPopupClosed()),this.cleanup())},500)}sendStatusToIframe(e){if(!this.opts.iframeWindow)return;const t={source:"moment-sdk",type:"moment.embed.oauth.status",status:e};this.opts.iframeWindow.postMessage(t,this.opts.liveOrigin)}}function T(s,e,t=typeof window<"u"?window.location.origin:""){if(e.triggerType==="schedule"){const o=new URLSearchParams;return o.set("teamSlug",e.teamSlug),o.set("returnOrigin",t),o.set("embed","1"),`${s}/en/embed/schedule?${o.toString()}`}const i=new URLSearchParams;return i.set("teamSlug",e.teamSlug),e.momentSlug&&i.set("momentSlug",e.momentSlug),e.listSlug&&i.set("listSlug",e.listSlug),i.set("triggerType",e.triggerType),e.ids?.length&&i.set("ids",e.ids.join(",")),e.playlistSlugs?.length&&i.set("playlistSlugs",e.playlistSlugs.join(",")),e.calendar&&i.set("calendar",e.calendar),e.apiSourceId&&i.set("subscriptionId",e.apiSourceId),e.externalEventId&&i.set("externalId",e.externalEventId),e.externalEventIdPrefix&&!e.externalEventId&&i.set("externalIdPrefix",e.externalEventIdPrefix),i.set("returnOrigin",t),i.set("embed","1"),`${s}/en/embed/sync?${i.toString()}`}const b="moment-sdk-trigger-styles",x=`
|
|
2
2
|
:where(.moment-sync-trigger) {
|
|
3
3
|
background-color: #1c1917;
|
|
4
4
|
color: #ffffff;
|
|
@@ -22,4 +22,4 @@
|
|
|
22
22
|
outline: 2px solid #1c1917;
|
|
23
23
|
outline-offset: 2px;
|
|
24
24
|
}
|
|
25
|
-
`;function T(){if(typeof document>"u"||document.getElementById(v))return;const n=document.createElement("style");n.id=v,n.textContent=S,document.head.appendChild(n)}const E=".moment-sync-trigger",I="https://live.momentco.ai";class b{opts;liveBaseUrl;liveOrigin;overlay=null;iframe=null;popupBridge=null;messageHandler=null;boundClickHandlers=new Map;activePayload=null;previousFocusedElement=null;previousBodyOverflow="";didManageBodyOverflow=!1;closeTimer=null;constructor(e){this.opts={triggerSelector:E,...e},this.liveBaseUrl=I;try{this.liveOrigin=new URL(this.liveBaseUrl).origin}catch{throw new Error(`[MomentSdk] Invalid VITE_LIVE_BASE_URL: "${this.liveBaseUrl}"`)}T(),this.bindTriggers(),this.emitAnalytics("embed.init")}open(e){e.externalEventId&&e.externalEventIdPrefix&&console.warn("[MomentSdk] Both externalEventId and externalEventIdPrefix provided; using externalEventId only.");const t=this.scopeOpenPayload(e);this.emitAnalytics("embed.open",{teamSlug:t.teamSlug,triggerType:t.triggerType}),this.createModal(t)}close(){this.handleCancel()}rebind(){this.unbindTriggers(),this.bindTriggers()}destroy(){this.cleanup(),this.unbindTriggers()}bindTriggers(){document.querySelectorAll(this.opts.triggerSelector).forEach(t=>{const i=s=>{s.preventDefault(),this.handleTriggerClick(t)},l=this.boundClickHandlers.get(t);l&&t.removeEventListener("click",l),t.addEventListener("click",i),this.boundClickHandlers.set(t,i)})}unbindTriggers(){this.boundClickHandlers.forEach((e,t)=>{t.removeEventListener("click",e)}),this.boundClickHandlers.clear()}handleTriggerClick(e){const t=e.getAttribute("data-moment-team-slug")??"",i=e.getAttribute("data-moment-slug")??void 0,l=e.getAttribute("data-moment-list-slug")??void 0,s=e.getAttribute("data-moment-trigger-type");let r="moment";s!=null&&s!==""&&(s==="moment"||s==="list"||s==="team"||s==="brand-playlist"||s==="playlist"?r=s:console.warn("[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",s));const a=e.getAttribute("data-moment-ids")??e.getAttribute("data-moment-game-id")??"",m=a?a.split(",").filter(Boolean):[],d=e.getAttribute("data-moment-playlist-slugs")??"",g=d?d.split(",").map(A=>A.trim()).filter(Boolean):[],o=e.getAttribute("data-moment-calendar");let u;o!=null&&o!==""&&(o==="google"||o==="outlook"?u=o:console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:",o));const h=e.getAttribute("data-api-source-id")??void 0,c=e.getAttribute("data-external-event-id")??void 0,p=e.getAttribute("data-external-event-id-prefix")??void 0;if(c&&p&&console.warn("[MomentSdk] Both data-external-event-id and data-external-event-id-prefix are set; using data-external-event-id only."),!t){console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");return}const P=r==="team"||r==="brand-playlist"||r==="playlist";this.emitAnalytics("embed.trigger.click",{teamSlug:t,triggerType:r,momentIds:P?[]:m}),this.open({teamSlug:t,momentSlug:i,listSlug:l,triggerType:r,ids:m,playlistSlugs:g,calendar:u??void 0,apiSourceId:h,externalEventId:p&&!c?void 0:c,externalEventIdPrefix:c?void 0:p})}scopeOpenPayload(e){return e.triggerType==="playlist"?{teamSlug:e.teamSlug,triggerType:e.triggerType,playlistSlugs:e.playlistSlugs,calendar:e.calendar}:e.triggerType==="team"||e.triggerType==="brand-playlist"?{teamSlug:e.teamSlug,triggerType:e.triggerType,calendar:e.calendar}:e}createModal(e){this.cleanup(),this.previousFocusedElement=document.activeElement||null,this.activePayload=e;const t=this.buildIframeUrl(e);this.overlay=document.createElement("div"),this.overlay.setAttribute("role","presentation"),Object.assign(this.overlay.style,{position:"fixed",inset:"0",zIndex:"999999",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"rgba(0, 0, 0, 0.5)",backdropFilter:"blur(2px)",padding:"16px"}),this.overlay.addEventListener("click",o=>{o.target===this.overlay&&this.handleCancel()});const i=o=>{o.key==="Escape"&&this.handleCancel()};document.addEventListener("keydown",i);const l=document.createElement("div");Object.assign(l.style,{position:"relative",width:"100%",maxWidth:"380px",borderRadius:"16px",overflow:"hidden",backgroundColor:"#000000",boxShadow:"0 25px 50px -12px rgba(0, 0, 0, 0.25)",display:"flex",flexDirection:"column"});const s=document.createElement("div");s.setAttribute("role","dialog"),s.setAttribute("aria-modal","true"),s.setAttribute("aria-label","Calendar sync modal"),s.tabIndex=-1,Object.assign(s.style,{position:"relative",width:"100%",maxHeight:"80vh",borderRadius:"12px",overflow:"auto",backgroundColor:"#ffffff"});const r=document.createElement("button");r.type="button",r.innerHTML='<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>',r.setAttribute("aria-label","Close"),Object.assign(r.style,{position:"absolute",top:"10px",right:"10px",zIndex:"10",border:"none",backgroundColor:"transparent",color:"#999",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",padding:"0"}),r.addEventListener("mouseenter",()=>{r.style.color="#666"}),r.addEventListener("mouseleave",()=>{r.style.color="#999"}),r.addEventListener("click",()=>this.handleCancel()),this.iframe=document.createElement("iframe"),this.iframe.src=t,this.iframe.setAttribute("allow",""),this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-popups allow-forms"),this.iframe.setAttribute("scrolling","no"),Object.assign(this.iframe.style,{width:"100%",border:"none",display:"block",height:"420px",overflow:"hidden",transition:"height 0.15s ease"}),s.appendChild(r),s.appendChild(this.iframe);const a=document.createElement("div");Object.assign(a.style,{display:"flex",alignItems:"center",justifyContent:"center",gap:"6px",padding:"12px 0"});const m=document.createElement("span");m.textContent="Powered by",Object.assign(m.style,{fontFamily:"Inter, system-ui, -apple-system, sans-serif",fontSize:"12px",lineHeight:"16px",color:"#ffffff"});const d=document.createElement("img");d.alt="Moment",d.src=`${this.liveBaseUrl}/brand/moment-text-white.png`,Object.assign(d.style,{height:"auto",width:"67px"}),a.appendChild(m),a.appendChild(d),l.appendChild(s),l.appendChild(a),this.overlay.appendChild(l),document.body.appendChild(this.overlay);const g=o=>{if(o.key!=="Tab")return;const u=s.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(u.length===0){o.preventDefault(),s.focus();return}const h=u[0],c=u[u.length-1],p=document.activeElement;o.shiftKey&&p===h?(o.preventDefault(),c.focus()):!o.shiftKey&&p===c&&(o.preventDefault(),h.focus())};s.addEventListener("keydown",g),r.focus(),this.previousBodyOverflow=document.body.style.overflow,document.body.style.overflow="hidden",this.didManageBodyOverflow=!0,this.setupMessageListener(),this.overlay._escHandler=i,this.overlay._focusTrapHandler=g}buildIframeUrl(e){return x(this.liveBaseUrl,e)}setupMessageListener(){this.messageHandler=e=>{if(e.origin!==this.liveOrigin||e.source!==this.iframe?.contentWindow)return;const t=e.data;if(!(!t||typeof t!="object")){if(t.source==="moment-live-embed"&&t.type==="moment.embed.oauth.start"){const i=t;this.emitAnalytics("oauth.start",{provider:i.provider,teamSlug:i.teamSlug}),this.handleOAuthStart(i);return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.resize"){const i=typeof t.height=="number"?t.height:0;if(i>0&&this.iframe){const l=Math.round(window.innerHeight*.8);this.iframe.style.height=`${Math.min(i,l)}px`}return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.open.url"){const{url:i}=t;i&&typeof i=="string"&&i.startsWith("webcal://")&&window.open(i,"_blank");return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.result"){const i=t;this.handleResult(i)}}},window.addEventListener("message",this.messageHandler)}handleOAuthStart(e){this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.popupBridge=new w({liveBaseUrl:this.liveBaseUrl,liveOrigin:this.liveOrigin,iframeWindow:this.iframe?.contentWindow??null,onResult:t=>{this.handleResult(t)},onPopupBlocked:()=>{this.emitAnalytics("oauth.popup.blocked",{provider:e.provider})},onPopupClosed:()=>{this.emitAnalytics("oauth.popup.closed",{provider:e.provider})}}),this.popupBridge.open(e)}handleResult(e){if(e.result==="success")this.emitAnalytics("subscribe.success",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onSuccess?.(e);else if(e.result==="error")this.emitAnalytics("subscribe.error",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onError?.(e);else if(e.result==="cancelled")return;this.opts.onClose?.(e),this.emitAnalytics("embed.close"),this.closeTimer=setTimeout(()=>this.cleanup(),1500)}handleCancel(){this.emitAnalytics("embed.close");const t={source:"moment-live-embed",type:"moment.embed.result",result:"cancelled",triggerType:this.activePayload?.triggerType??"moment",momentIds:this.activePayload?.ids??[]};this.opts.onClose?.(t),this.cleanup()}cleanup(){if(this.closeTimer&&(clearTimeout(this.closeTimer),this.closeTimer=null),this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.overlay){const{_escHandler:e,_focusTrapHandler:t}=this.overlay;e&&document.removeEventListener("keydown",e);const i=this.overlay.firstElementChild;i&&t&&i.removeEventListener("keydown",t),this.overlay.remove(),this.overlay=null}this.iframe=null,this.activePayload=null,this.didManageBodyOverflow&&(document.body.style.overflow=this.previousBodyOverflow,this.didManageBodyOverflow=!1),this.previousFocusedElement&&typeof this.previousFocusedElement.focus=="function"&&this.previousFocusedElement.focus(),this.previousFocusedElement=null}emitAnalytics(e,t){this.opts.onAnalytics?.({event:e,timestamp:Date.now(),...t})}}function y(n){const e=document.createElement("button");return e.type="button",e.classList.add("moment-sync-trigger"),n.className&&n.className.split(" ").forEach(t=>{t&&e.classList.add(t)}),e.setAttribute("data-moment-team-slug",n.teamSlug),n.momentSlug&&e.setAttribute("data-moment-slug",n.momentSlug),n.listSlug&&e.setAttribute("data-moment-list-slug",n.listSlug),n.triggerType&&e.setAttribute("data-moment-trigger-type",n.triggerType),n.ids?.length&&e.setAttribute("data-moment-ids",n.ids.join(",")),n.playlistSlugs?.length&&e.setAttribute("data-moment-playlist-slugs",n.playlistSlugs.join(",")),n.calendar&&e.setAttribute("data-moment-calendar",n.calendar),n.apiSourceId&&e.setAttribute("data-api-source-id",n.apiSourceId),n.externalEventId&&e.setAttribute("data-external-event-id",n.externalEventId),n.externalEventIdPrefix&&e.setAttribute("data-external-event-id-prefix",n.externalEventIdPrefix),e.textContent=n.label??"Add to Calendar",e}if(typeof window<"u"){const n=window;n.MomentSdk=b,n.createMomentButton=y}f.MomentSdk=b,f.createMomentButton=y,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})})(this.MomentSdk=this.MomentSdk||{});
|
|
25
|
+
`;function E(){if(typeof document>"u"||document.getElementById(b))return;const s=document.createElement("style");s.id=b,s.textContent=x,document.head.appendChild(s)}const I=".moment-sync-trigger",P="https://live.dev.momentco.ai";class y{opts;liveBaseUrl;liveOrigin;overlay=null;iframe=null;popupBridge=null;messageHandler=null;boundClickHandlers=new Map;activePayload=null;previousFocusedElement=null;previousBodyOverflow="";didManageBodyOverflow=!1;closeTimer=null;constructor(e){this.opts={triggerSelector:I,...e},this.liveBaseUrl=P;try{this.liveOrigin=new URL(this.liveBaseUrl).origin}catch{throw new Error(`[MomentSdk] Invalid VITE_LIVE_BASE_URL: "${this.liveBaseUrl}"`)}E(),this.bindTriggers(),this.emitAnalytics("embed.init")}open(e){e.externalEventId&&e.externalEventIdPrefix&&console.warn("[MomentSdk] Both externalEventId and externalEventIdPrefix provided; using externalEventId only.");const t=this.scopeOpenPayload(e);this.emitAnalytics("embed.open",{teamSlug:t.teamSlug,triggerType:t.triggerType}),this.createModal(t)}close(){this.handleCancel()}rebind(){this.unbindTriggers(),this.bindTriggers()}destroy(){this.cleanup(),this.unbindTriggers()}bindTriggers(){document.querySelectorAll(this.opts.triggerSelector).forEach(t=>{const i=n=>{n.preventDefault(),this.handleTriggerClick(t)},o=this.boundClickHandlers.get(t);o&&t.removeEventListener("click",o),t.addEventListener("click",i),this.boundClickHandlers.set(t,i)})}unbindTriggers(){this.boundClickHandlers.forEach((e,t)=>{t.removeEventListener("click",e)}),this.boundClickHandlers.clear()}handleTriggerClick(e){const t=e.getAttribute("data-moment-team-slug")??"",i=e.getAttribute("data-moment-slug")??void 0,o=e.getAttribute("data-moment-list-slug")??void 0,n=e.getAttribute("data-moment-trigger-type");let r="moment";n!=null&&n!==""&&(n==="moment"||n==="list"||n==="team"||n==="brand-playlist"||n==="playlist"||n==="schedule"?r=n:console.warn("[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",n));const h=e.getAttribute("data-moment-ids")??e.getAttribute("data-moment-game-id")??"",d=h?h.split(",").filter(Boolean):[],c=e.getAttribute("data-moment-playlist-slugs")??"",m=c?c.split(",").map(k=>k.trim()).filter(Boolean):[],a=e.getAttribute("data-moment-calendar");let l;a!=null&&a!==""&&(a==="google"||a==="outlook"?l=a:console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:",a));const p=e.getAttribute("data-api-source-id")??void 0,u=e.getAttribute("data-external-event-id")??void 0,g=e.getAttribute("data-external-event-id-prefix")??void 0;if(u&&g&&console.warn("[MomentSdk] Both data-external-event-id and data-external-event-id-prefix are set; using data-external-event-id only."),!t){console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");return}const f=r==="team"||r==="brand-playlist"||r==="playlist"||r==="schedule";this.emitAnalytics("embed.trigger.click",{teamSlug:t,triggerType:r,momentIds:f?[]:d}),this.open({teamSlug:t,momentSlug:i,listSlug:o,triggerType:r,ids:d,playlistSlugs:m,calendar:l??void 0,apiSourceId:p,externalEventId:g&&!u?void 0:u,externalEventIdPrefix:u?void 0:g})}scopeOpenPayload(e){return e.triggerType==="schedule"?{teamSlug:e.teamSlug,triggerType:e.triggerType}:e.triggerType==="playlist"?{teamSlug:e.teamSlug,triggerType:e.triggerType,playlistSlugs:e.playlistSlugs,calendar:e.calendar}:e.triggerType==="team"||e.triggerType==="brand-playlist"?{teamSlug:e.teamSlug,triggerType:e.triggerType,calendar:e.calendar}:e}createModal(e){this.cleanup(),this.previousFocusedElement=document.activeElement||null,this.activePayload=e;const t=this.buildIframeUrl(e);this.overlay=document.createElement("div"),this.overlay.setAttribute("role","presentation"),Object.assign(this.overlay.style,{position:"fixed",inset:"0",zIndex:"999999",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"rgba(0, 0, 0, 0.5)",backdropFilter:"blur(2px)",padding:"16px"}),this.overlay.addEventListener("click",l=>{l.target===this.overlay&&this.handleCancel()});const i=l=>{l.key==="Escape"&&this.handleCancel()};document.addEventListener("keydown",i);const o=document.createElement("div");Object.assign(o.style,{position:"relative",width:"100%",maxWidth:"380px",borderRadius:"16px",overflow:"hidden",backgroundColor:"#000000",boxShadow:"0 25px 50px -12px rgba(0, 0, 0, 0.25)",display:"flex",flexDirection:"column"});const n=document.createElement("div");n.setAttribute("role","dialog"),n.setAttribute("aria-modal","true"),n.setAttribute("aria-label","Calendar sync modal"),n.tabIndex=-1,Object.assign(n.style,{position:"relative",width:"100%",maxHeight:"80vh",borderRadius:"12px",overflow:"auto",backgroundColor:"#ffffff"});const r=document.createElement("button");r.type="button",r.innerHTML='<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>',r.setAttribute("aria-label","Close"),Object.assign(r.style,{position:"absolute",top:"10px",right:"10px",zIndex:"10",border:"none",backgroundColor:"transparent",color:"#999",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",padding:"0"}),r.addEventListener("mouseenter",()=>{r.style.color="#666"}),r.addEventListener("mouseleave",()=>{r.style.color="#999"}),r.addEventListener("click",()=>this.handleCancel());const h=e.triggerType!=="schedule";this.iframe=document.createElement("iframe"),this.iframe.src=t,this.iframe.setAttribute("allow",""),this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-popups allow-forms"),this.iframe.setAttribute("scrolling","no"),Object.assign(this.iframe.style,{width:"100%",border:"none",display:"block",height:"420px",overflow:"hidden",transition:"height 0.15s ease"}),h&&n.appendChild(r),n.appendChild(this.iframe);const d=document.createElement("div");Object.assign(d.style,{display:"flex",alignItems:"center",justifyContent:"center",gap:"6px",padding:"12px 0"});const c=document.createElement("span");c.textContent="Powered by",Object.assign(c.style,{fontFamily:"Inter, system-ui, -apple-system, sans-serif",fontSize:"12px",lineHeight:"16px",color:"#ffffff"});const m=document.createElement("img");m.alt="Moment",m.src=`${this.liveBaseUrl}/brand/moment-text-white.png`,Object.assign(m.style,{height:"auto",width:"67px"}),d.appendChild(c),d.appendChild(m),o.appendChild(n),o.appendChild(d),this.overlay.appendChild(o),document.body.appendChild(this.overlay);const a=l=>{if(l.key!=="Tab")return;const p=n.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(p.length===0){l.preventDefault(),n.focus();return}const u=p[0],g=p[p.length-1],f=document.activeElement;l.shiftKey&&f===u?(l.preventDefault(),g.focus()):!l.shiftKey&&f===g&&(l.preventDefault(),u.focus())};n.addEventListener("keydown",a),h?r.focus():n.focus(),this.previousBodyOverflow=document.body.style.overflow,document.body.style.overflow="hidden",this.didManageBodyOverflow=!0,this.setupMessageListener(),this.overlay._escHandler=i,this.overlay._focusTrapHandler=a}buildIframeUrl(e){return T(this.liveBaseUrl,e)}setupMessageListener(){this.messageHandler=e=>{if(e.origin!==this.liveOrigin||e.source!==this.iframe?.contentWindow)return;const t=e.data;if(!(!t||typeof t!="object")){if(t.source==="moment-live-embed"&&t.type==="moment.embed.oauth.start"){const i=t;this.emitAnalytics("oauth.start",{provider:i.provider,teamSlug:i.teamSlug}),this.handleOAuthStart(i);return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.resize"){const i=typeof t.height=="number"?t.height:0;if(i>0&&this.iframe){const o=Math.round(window.innerHeight*.8);this.iframe.style.height=`${Math.min(i,o)}px`}return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.open.url"){const{url:i}=t;i&&typeof i=="string"&&i.startsWith("webcal://")&&window.open(i,"_blank");return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.close"){this.handleCancel();return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.result"){const i=t;this.handleResult(i)}}},window.addEventListener("message",this.messageHandler)}handleOAuthStart(e){this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.popupBridge=new S({liveBaseUrl:this.liveBaseUrl,liveOrigin:this.liveOrigin,iframeWindow:this.iframe?.contentWindow??null,onResult:t=>{this.handleResult(t)},onPopupBlocked:()=>{this.emitAnalytics("oauth.popup.blocked",{provider:e.provider})},onPopupClosed:()=>{this.emitAnalytics("oauth.popup.closed",{provider:e.provider})}}),this.popupBridge.open(e)}handleResult(e){if(e.result==="success"||e.result==="partial")this.emitAnalytics("subscribe.success",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onSuccess?.(e);else if(e.result==="error")this.emitAnalytics("subscribe.error",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onError?.(e);else if(e.result==="cancelled")return;this.opts.onClose?.(e),this.emitAnalytics("embed.close"),e.triggerType!=="schedule"&&(this.closeTimer=setTimeout(()=>this.cleanup(),1500))}handleCancel(){this.emitAnalytics("embed.close");const t={source:"moment-live-embed",type:"moment.embed.result",result:"cancelled",triggerType:this.activePayload?.triggerType??"moment",momentIds:this.activePayload?.ids??[]};this.opts.onClose?.(t),this.cleanup()}cleanup(){if(this.closeTimer&&(clearTimeout(this.closeTimer),this.closeTimer=null),this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.overlay){const{_escHandler:e,_focusTrapHandler:t}=this.overlay;e&&document.removeEventListener("keydown",e);const i=this.overlay.firstElementChild;i&&t&&i.removeEventListener("keydown",t),this.overlay.remove(),this.overlay=null}this.iframe=null,this.activePayload=null,this.didManageBodyOverflow&&(document.body.style.overflow=this.previousBodyOverflow,this.didManageBodyOverflow=!1),this.previousFocusedElement&&typeof this.previousFocusedElement.focus=="function"&&this.previousFocusedElement.focus(),this.previousFocusedElement=null}emitAnalytics(e,t){this.opts.onAnalytics?.({event:e,timestamp:Date.now(),...t})}}function w(s){const e=document.createElement("button");return e.type="button",e.classList.add("moment-sync-trigger"),s.className&&s.className.split(" ").forEach(t=>{t&&e.classList.add(t)}),e.setAttribute("data-moment-team-slug",s.teamSlug),s.momentSlug&&e.setAttribute("data-moment-slug",s.momentSlug),s.listSlug&&e.setAttribute("data-moment-list-slug",s.listSlug),s.triggerType&&e.setAttribute("data-moment-trigger-type",s.triggerType),s.ids?.length&&e.setAttribute("data-moment-ids",s.ids.join(",")),s.playlistSlugs?.length&&e.setAttribute("data-moment-playlist-slugs",s.playlistSlugs.join(",")),s.calendar&&e.setAttribute("data-moment-calendar",s.calendar),s.apiSourceId&&e.setAttribute("data-api-source-id",s.apiSourceId),s.externalEventId&&e.setAttribute("data-external-event-id",s.externalEventId),s.externalEventIdPrefix&&e.setAttribute("data-external-event-id-prefix",s.externalEventIdPrefix),e.textContent=s.label??"Add to Calendar",e}if(typeof window<"u"){const s=window;s.MomentSdk=y,s.createMomentButton=w}v.MomentSdk=y,v.createMomentButton=w,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})})(this.MomentSdk=this.MomentSdk||{});
|
package/dist/moment-sdk.mjs
CHANGED
|
@@ -12,18 +12,18 @@ class y {
|
|
|
12
12
|
*/
|
|
13
13
|
open(e) {
|
|
14
14
|
this.cleanup(), this.resultReceived = !1;
|
|
15
|
-
const t = this.buildPopupUrl(e), i = Math.round(window.screenX + (window.outerWidth - 500) / 2),
|
|
15
|
+
const t = this.buildPopupUrl(e), i = Math.round(window.screenX + (window.outerWidth - 500) / 2), o = Math.round(window.screenY + (window.outerHeight - 700) / 2);
|
|
16
16
|
if (this.popup = window.open(
|
|
17
17
|
t,
|
|
18
18
|
"moment-oauth-popup",
|
|
19
|
-
`width=500,height=700,left=${i},top=${
|
|
19
|
+
`width=500,height=700,left=${i},top=${o},menubar=no,toolbar=no,location=yes,status=no`
|
|
20
20
|
), !this.popup || this.popup.closed) {
|
|
21
21
|
this.sendStatusToIframe("popup-blocked"), this.opts.onPopupBlocked();
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
this.sendStatusToIframe("popup-opened"), this.startPolling(), this.messageHandler = (
|
|
25
|
-
if (
|
|
26
|
-
const r =
|
|
24
|
+
this.sendStatusToIframe("popup-opened"), this.startPolling(), this.messageHandler = (n) => {
|
|
25
|
+
if (n.origin !== this.opts.liveOrigin || n.source !== this.popup) return;
|
|
26
|
+
const r = n.data;
|
|
27
27
|
!r || typeof r != "object" || r.source !== "moment-live-embed" || r.type !== "moment.embed.result" || (this.resultReceived = !0, this.sendStatusToIframe("completed"), this.opts.iframeWindow && this.opts.iframeWindow.postMessage(r, this.opts.liveOrigin), this.opts.onResult(r), this.cleanup());
|
|
28
28
|
}, window.addEventListener("message", this.messageHandler);
|
|
29
29
|
}
|
|
@@ -46,7 +46,7 @@ class y {
|
|
|
46
46
|
triggerType: e.triggerType,
|
|
47
47
|
returnOrigin: t
|
|
48
48
|
});
|
|
49
|
-
return e.subscriptionType && i.set("subscriptionType", e.subscriptionType), `${this.opts.liveBaseUrl}/en/embed/oauth-popup?${i.toString()}`;
|
|
49
|
+
return e.subscriptionType && i.set("subscriptionType", e.subscriptionType), e.scheduleMomentIds?.length && i.set("scheduleMomentIds", e.scheduleMomentIds.join(",")), e.schedulePlaylistIds?.length && i.set("schedulePlaylistIds", e.schedulePlaylistIds.join(",")), `${this.opts.liveBaseUrl}/en/embed/oauth-popup?${i.toString()}`;
|
|
50
50
|
}
|
|
51
51
|
startPolling() {
|
|
52
52
|
this.pollTimer = setInterval(() => {
|
|
@@ -63,11 +63,15 @@ class y {
|
|
|
63
63
|
this.opts.iframeWindow.postMessage(t, this.opts.liveOrigin);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
function w(
|
|
66
|
+
function w(s, e, t = typeof window < "u" ? window.location.origin : "") {
|
|
67
|
+
if (e.triggerType === "schedule") {
|
|
68
|
+
const o = new URLSearchParams();
|
|
69
|
+
return o.set("teamSlug", e.teamSlug), o.set("returnOrigin", t), o.set("embed", "1"), `${s}/en/embed/schedule?${o.toString()}`;
|
|
70
|
+
}
|
|
67
71
|
const i = new URLSearchParams();
|
|
68
|
-
return i.set("teamSlug", e.teamSlug), e.momentSlug && i.set("momentSlug", e.momentSlug), e.listSlug && i.set("listSlug", e.listSlug), i.set("triggerType", e.triggerType), e.ids?.length && i.set("ids", e.ids.join(",")), e.playlistSlugs?.length && i.set("playlistSlugs", e.playlistSlugs.join(",")), e.calendar && i.set("calendar", e.calendar), e.apiSourceId && i.set("subscriptionId", e.apiSourceId), e.externalEventId && i.set("externalId", e.externalEventId), e.externalEventIdPrefix && !e.externalEventId && i.set("externalIdPrefix", e.externalEventIdPrefix), i.set("returnOrigin", t), i.set("embed", "1"), `${
|
|
72
|
+
return i.set("teamSlug", e.teamSlug), e.momentSlug && i.set("momentSlug", e.momentSlug), e.listSlug && i.set("listSlug", e.listSlug), i.set("triggerType", e.triggerType), e.ids?.length && i.set("ids", e.ids.join(",")), e.playlistSlugs?.length && i.set("playlistSlugs", e.playlistSlugs.join(",")), e.calendar && i.set("calendar", e.calendar), e.apiSourceId && i.set("subscriptionId", e.apiSourceId), e.externalEventId && i.set("externalId", e.externalEventId), e.externalEventIdPrefix && !e.externalEventId && i.set("externalIdPrefix", e.externalEventIdPrefix), i.set("returnOrigin", t), i.set("embed", "1"), `${s}/en/embed/sync?${i.toString()}`;
|
|
69
73
|
}
|
|
70
|
-
const
|
|
74
|
+
const v = "moment-sdk-trigger-styles", x = `
|
|
71
75
|
:where(.moment-sync-trigger) {
|
|
72
76
|
background-color: #1c1917;
|
|
73
77
|
color: #ffffff;
|
|
@@ -93,11 +97,11 @@ const f = "moment-sdk-trigger-styles", x = `
|
|
|
93
97
|
}
|
|
94
98
|
`;
|
|
95
99
|
function T() {
|
|
96
|
-
if (typeof document > "u" || document.getElementById(
|
|
97
|
-
const
|
|
98
|
-
|
|
100
|
+
if (typeof document > "u" || document.getElementById(v)) return;
|
|
101
|
+
const s = document.createElement("style");
|
|
102
|
+
s.id = v, s.textContent = x, document.head.appendChild(s);
|
|
99
103
|
}
|
|
100
|
-
const
|
|
104
|
+
const S = ".moment-sync-trigger", E = "https://live.dev.momentco.ai";
|
|
101
105
|
class I {
|
|
102
106
|
opts;
|
|
103
107
|
liveBaseUrl;
|
|
@@ -114,9 +118,9 @@ class I {
|
|
|
114
118
|
closeTimer = null;
|
|
115
119
|
constructor(e) {
|
|
116
120
|
this.opts = {
|
|
117
|
-
triggerSelector:
|
|
121
|
+
triggerSelector: S,
|
|
118
122
|
...e
|
|
119
|
-
}, this.liveBaseUrl =
|
|
123
|
+
}, this.liveBaseUrl = E;
|
|
120
124
|
try {
|
|
121
125
|
this.liveOrigin = new URL(this.liveBaseUrl).origin;
|
|
122
126
|
} catch {
|
|
@@ -158,10 +162,10 @@ class I {
|
|
|
158
162
|
// ── Private: trigger binding ──────────────────────────────────────
|
|
159
163
|
bindTriggers() {
|
|
160
164
|
document.querySelectorAll(this.opts.triggerSelector).forEach((t) => {
|
|
161
|
-
const i = (
|
|
162
|
-
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
+
const i = (n) => {
|
|
166
|
+
n.preventDefault(), this.handleTriggerClick(t);
|
|
167
|
+
}, o = this.boundClickHandlers.get(t);
|
|
168
|
+
o && t.removeEventListener("click", o), t.addEventListener("click", i), this.boundClickHandlers.set(t, i);
|
|
165
169
|
});
|
|
166
170
|
}
|
|
167
171
|
unbindTriggers() {
|
|
@@ -170,43 +174,46 @@ class I {
|
|
|
170
174
|
}), this.boundClickHandlers.clear();
|
|
171
175
|
}
|
|
172
176
|
handleTriggerClick(e) {
|
|
173
|
-
const t = e.getAttribute("data-moment-team-slug") ?? "", i = e.getAttribute("data-moment-slug") ?? void 0,
|
|
177
|
+
const t = e.getAttribute("data-moment-team-slug") ?? "", i = e.getAttribute("data-moment-slug") ?? void 0, o = e.getAttribute("data-moment-list-slug") ?? void 0, n = e.getAttribute("data-moment-trigger-type");
|
|
174
178
|
let r = "moment";
|
|
175
|
-
|
|
179
|
+
n != null && n !== "" && (n === "moment" || n === "list" || n === "team" || n === "brand-playlist" || n === "playlist" || n === "schedule" ? r = n : console.warn(
|
|
176
180
|
"[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",
|
|
177
|
-
|
|
181
|
+
n
|
|
178
182
|
));
|
|
179
|
-
const
|
|
180
|
-
let
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
183
|
+
const h = e.getAttribute("data-moment-ids") ?? e.getAttribute("data-moment-game-id") ?? "", d = h ? h.split(",").filter(Boolean) : [], c = e.getAttribute("data-moment-playlist-slugs") ?? "", m = c ? c.split(",").map((b) => b.trim()).filter(Boolean) : [], a = e.getAttribute("data-moment-calendar");
|
|
184
|
+
let l;
|
|
185
|
+
a != null && a !== "" && (a === "google" || a === "outlook" ? l = a : console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:", a));
|
|
186
|
+
const p = e.getAttribute("data-api-source-id") ?? void 0, u = e.getAttribute("data-external-event-id") ?? void 0, g = e.getAttribute("data-external-event-id-prefix") ?? void 0;
|
|
187
|
+
if (u && g && console.warn(
|
|
184
188
|
"[MomentSdk] Both data-external-event-id and data-external-event-id-prefix are set; using data-external-event-id only."
|
|
185
189
|
), !t) {
|
|
186
190
|
console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");
|
|
187
191
|
return;
|
|
188
192
|
}
|
|
189
|
-
const
|
|
193
|
+
const f = r === "team" || r === "brand-playlist" || r === "playlist" || r === "schedule";
|
|
190
194
|
this.emitAnalytics("embed.trigger.click", {
|
|
191
195
|
teamSlug: t,
|
|
192
196
|
triggerType: r,
|
|
193
|
-
momentIds:
|
|
197
|
+
momentIds: f ? [] : d
|
|
194
198
|
}), this.open({
|
|
195
199
|
teamSlug: t,
|
|
196
200
|
momentSlug: i,
|
|
197
|
-
listSlug:
|
|
201
|
+
listSlug: o,
|
|
198
202
|
triggerType: r,
|
|
199
|
-
ids:
|
|
200
|
-
playlistSlugs:
|
|
201
|
-
calendar:
|
|
202
|
-
apiSourceId:
|
|
203
|
-
externalEventId:
|
|
204
|
-
externalEventIdPrefix:
|
|
203
|
+
ids: d,
|
|
204
|
+
playlistSlugs: m,
|
|
205
|
+
calendar: l ?? void 0,
|
|
206
|
+
apiSourceId: p,
|
|
207
|
+
externalEventId: g && !u ? void 0 : u,
|
|
208
|
+
externalEventIdPrefix: u ? void 0 : g
|
|
205
209
|
});
|
|
206
210
|
}
|
|
207
211
|
/** Drop moment/list/source scoping for brand-level subscription triggers. */
|
|
208
212
|
scopeOpenPayload(e) {
|
|
209
|
-
return e.triggerType === "
|
|
213
|
+
return e.triggerType === "schedule" ? {
|
|
214
|
+
teamSlug: e.teamSlug,
|
|
215
|
+
triggerType: e.triggerType
|
|
216
|
+
} : e.triggerType === "playlist" ? {
|
|
210
217
|
teamSlug: e.teamSlug,
|
|
211
218
|
triggerType: e.triggerType,
|
|
212
219
|
playlistSlugs: e.playlistSlugs,
|
|
@@ -231,15 +238,15 @@ class I {
|
|
|
231
238
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
232
239
|
backdropFilter: "blur(2px)",
|
|
233
240
|
padding: "16px"
|
|
234
|
-
}), this.overlay.addEventListener("click", (
|
|
235
|
-
|
|
241
|
+
}), this.overlay.addEventListener("click", (l) => {
|
|
242
|
+
l.target === this.overlay && this.handleCancel();
|
|
236
243
|
});
|
|
237
|
-
const i = (
|
|
238
|
-
|
|
244
|
+
const i = (l) => {
|
|
245
|
+
l.key === "Escape" && this.handleCancel();
|
|
239
246
|
};
|
|
240
247
|
document.addEventListener("keydown", i);
|
|
241
|
-
const
|
|
242
|
-
Object.assign(
|
|
248
|
+
const o = document.createElement("div");
|
|
249
|
+
Object.assign(o.style, {
|
|
243
250
|
position: "relative",
|
|
244
251
|
width: "100%",
|
|
245
252
|
maxWidth: "380px",
|
|
@@ -250,8 +257,8 @@ class I {
|
|
|
250
257
|
display: "flex",
|
|
251
258
|
flexDirection: "column"
|
|
252
259
|
});
|
|
253
|
-
const
|
|
254
|
-
|
|
260
|
+
const n = document.createElement("div");
|
|
261
|
+
n.setAttribute("role", "dialog"), n.setAttribute("aria-modal", "true"), n.setAttribute("aria-label", "Calendar sync modal"), n.tabIndex = -1, Object.assign(n.style, {
|
|
255
262
|
position: "relative",
|
|
256
263
|
width: "100%",
|
|
257
264
|
maxHeight: "80vh",
|
|
@@ -279,47 +286,49 @@ class I {
|
|
|
279
286
|
r.style.color = "#666";
|
|
280
287
|
}), r.addEventListener("mouseleave", () => {
|
|
281
288
|
r.style.color = "#999";
|
|
282
|
-
}), r.addEventListener("click", () => this.handleCancel())
|
|
289
|
+
}), r.addEventListener("click", () => this.handleCancel());
|
|
290
|
+
const h = e.triggerType !== "schedule";
|
|
291
|
+
this.iframe = document.createElement("iframe"), this.iframe.src = t, this.iframe.setAttribute("allow", ""), this.iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-popups allow-forms"), this.iframe.setAttribute("scrolling", "no"), Object.assign(this.iframe.style, {
|
|
283
292
|
width: "100%",
|
|
284
293
|
border: "none",
|
|
285
294
|
display: "block",
|
|
286
295
|
height: "420px",
|
|
287
296
|
overflow: "hidden",
|
|
288
297
|
transition: "height 0.15s ease"
|
|
289
|
-
}),
|
|
290
|
-
const
|
|
291
|
-
Object.assign(
|
|
298
|
+
}), h && n.appendChild(r), n.appendChild(this.iframe);
|
|
299
|
+
const d = document.createElement("div");
|
|
300
|
+
Object.assign(d.style, {
|
|
292
301
|
display: "flex",
|
|
293
302
|
alignItems: "center",
|
|
294
303
|
justifyContent: "center",
|
|
295
304
|
gap: "6px",
|
|
296
305
|
padding: "12px 0"
|
|
297
306
|
});
|
|
298
|
-
const
|
|
299
|
-
|
|
307
|
+
const c = document.createElement("span");
|
|
308
|
+
c.textContent = "Powered by", Object.assign(c.style, {
|
|
300
309
|
fontFamily: "Inter, system-ui, -apple-system, sans-serif",
|
|
301
310
|
fontSize: "12px",
|
|
302
311
|
lineHeight: "16px",
|
|
303
312
|
color: "#ffffff"
|
|
304
313
|
});
|
|
305
|
-
const
|
|
306
|
-
|
|
314
|
+
const m = document.createElement("img");
|
|
315
|
+
m.alt = "Moment", m.src = `${this.liveBaseUrl}/brand/moment-text-white.png`, Object.assign(m.style, {
|
|
307
316
|
height: "auto",
|
|
308
317
|
width: "67px"
|
|
309
|
-
}),
|
|
310
|
-
const
|
|
311
|
-
if (
|
|
312
|
-
const
|
|
318
|
+
}), d.appendChild(c), d.appendChild(m), o.appendChild(n), o.appendChild(d), this.overlay.appendChild(o), document.body.appendChild(this.overlay);
|
|
319
|
+
const a = (l) => {
|
|
320
|
+
if (l.key !== "Tab") return;
|
|
321
|
+
const p = n.querySelectorAll(
|
|
313
322
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
314
323
|
);
|
|
315
|
-
if (
|
|
316
|
-
|
|
324
|
+
if (p.length === 0) {
|
|
325
|
+
l.preventDefault(), n.focus();
|
|
317
326
|
return;
|
|
318
327
|
}
|
|
319
|
-
const
|
|
320
|
-
|
|
328
|
+
const u = p[0], g = p[p.length - 1], f = document.activeElement;
|
|
329
|
+
l.shiftKey && f === u ? (l.preventDefault(), g.focus()) : !l.shiftKey && f === g && (l.preventDefault(), u.focus());
|
|
321
330
|
};
|
|
322
|
-
|
|
331
|
+
n.addEventListener("keydown", a), h ? r.focus() : n.focus(), this.previousBodyOverflow = document.body.style.overflow, document.body.style.overflow = "hidden", this.didManageBodyOverflow = !0, this.setupMessageListener(), this.overlay._escHandler = i, this.overlay._focusTrapHandler = a;
|
|
323
332
|
}
|
|
324
333
|
buildIframeUrl(e) {
|
|
325
334
|
return w(this.liveBaseUrl, e);
|
|
@@ -341,8 +350,8 @@ class I {
|
|
|
341
350
|
if (t.source === "moment-live-embed" && t.type === "moment.embed.resize") {
|
|
342
351
|
const i = typeof t.height == "number" ? t.height : 0;
|
|
343
352
|
if (i > 0 && this.iframe) {
|
|
344
|
-
const
|
|
345
|
-
this.iframe.style.height = `${Math.min(i,
|
|
353
|
+
const o = Math.round(window.innerHeight * 0.8);
|
|
354
|
+
this.iframe.style.height = `${Math.min(i, o)}px`;
|
|
346
355
|
}
|
|
347
356
|
return;
|
|
348
357
|
}
|
|
@@ -351,6 +360,10 @@ class I {
|
|
|
351
360
|
i && typeof i == "string" && i.startsWith("webcal://") && window.open(i, "_blank");
|
|
352
361
|
return;
|
|
353
362
|
}
|
|
363
|
+
if (t.source === "moment-live-embed" && t.type === "moment.embed.close") {
|
|
364
|
+
this.handleCancel();
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
354
367
|
if (t.source === "moment-live-embed" && t.type === "moment.embed.result") {
|
|
355
368
|
const i = t;
|
|
356
369
|
this.handleResult(i);
|
|
@@ -379,7 +392,7 @@ class I {
|
|
|
379
392
|
}), this.popupBridge.open(e);
|
|
380
393
|
}
|
|
381
394
|
handleResult(e) {
|
|
382
|
-
if (e.result === "success")
|
|
395
|
+
if (e.result === "success" || e.result === "partial")
|
|
383
396
|
this.emitAnalytics("subscribe.success", {
|
|
384
397
|
provider: e.provider,
|
|
385
398
|
triggerType: e.triggerType,
|
|
@@ -393,7 +406,7 @@ class I {
|
|
|
393
406
|
}), this.opts.onError?.(e);
|
|
394
407
|
else if (e.result === "cancelled")
|
|
395
408
|
return;
|
|
396
|
-
this.opts.onClose?.(e), this.emitAnalytics("embed.close"), this.closeTimer = setTimeout(() => this.cleanup(), 1500);
|
|
409
|
+
this.opts.onClose?.(e), this.emitAnalytics("embed.close"), e.triggerType !== "schedule" && (this.closeTimer = setTimeout(() => this.cleanup(), 1500));
|
|
397
410
|
}
|
|
398
411
|
handleCancel() {
|
|
399
412
|
this.emitAnalytics("embed.close");
|
|
@@ -425,15 +438,15 @@ class I {
|
|
|
425
438
|
});
|
|
426
439
|
}
|
|
427
440
|
}
|
|
428
|
-
function P(
|
|
441
|
+
function P(s) {
|
|
429
442
|
const e = document.createElement("button");
|
|
430
|
-
return e.type = "button", e.classList.add("moment-sync-trigger"),
|
|
443
|
+
return e.type = "button", e.classList.add("moment-sync-trigger"), s.className && s.className.split(" ").forEach((t) => {
|
|
431
444
|
t && e.classList.add(t);
|
|
432
|
-
}), e.setAttribute("data-moment-team-slug",
|
|
445
|
+
}), e.setAttribute("data-moment-team-slug", s.teamSlug), s.momentSlug && e.setAttribute("data-moment-slug", s.momentSlug), s.listSlug && e.setAttribute("data-moment-list-slug", s.listSlug), s.triggerType && e.setAttribute("data-moment-trigger-type", s.triggerType), s.ids?.length && e.setAttribute("data-moment-ids", s.ids.join(",")), s.playlistSlugs?.length && e.setAttribute("data-moment-playlist-slugs", s.playlistSlugs.join(",")), s.calendar && e.setAttribute("data-moment-calendar", s.calendar), s.apiSourceId && e.setAttribute("data-api-source-id", s.apiSourceId), s.externalEventId && e.setAttribute("data-external-event-id", s.externalEventId), s.externalEventIdPrefix && e.setAttribute("data-external-event-id-prefix", s.externalEventIdPrefix), e.textContent = s.label ?? "Add to Calendar", e;
|
|
433
446
|
}
|
|
434
447
|
if (typeof window < "u") {
|
|
435
|
-
const
|
|
436
|
-
|
|
448
|
+
const s = window;
|
|
449
|
+
s.MomentSdk = I, s.createMomentButton = P;
|
|
437
450
|
}
|
|
438
451
|
export {
|
|
439
452
|
I as MomentSdk,
|
|
@@ -9,9 +9,18 @@
|
|
|
9
9
|
* - `playlist` → subscribe to one of several specific playlists. When more than
|
|
10
10
|
* one slug is supplied, Live fetches each playlist and prompts the user to
|
|
11
11
|
* pick which one to sync.
|
|
12
|
+
* - `schedule` → open the brand's full schedule experience: a multi-select
|
|
13
|
+
* surface where the fan picks any mix of the brand's collections (playlists)
|
|
14
|
+
* and individual events (moments) and syncs them all at once.
|
|
12
15
|
*/
|
|
13
|
-
export type MomentTriggerType = 'moment' | 'list' | 'team' | 'brand-playlist' | 'playlist';
|
|
14
|
-
|
|
16
|
+
export type MomentTriggerType = 'moment' | 'list' | 'team' | 'brand-playlist' | 'playlist' | 'schedule';
|
|
17
|
+
/**
|
|
18
|
+
* Calendar providers. `google` / `outlook` are the OAuth providers a partner may
|
|
19
|
+
* pre-select via `data-moment-calendar`. `apple` is never a hint — it only ever
|
|
20
|
+
* appears on a result payload's `provider` (guest webcal path), so consumers
|
|
21
|
+
* switching on `provider` must tolerate it.
|
|
22
|
+
*/
|
|
23
|
+
export type CalendarProvider = 'google' | 'outlook' | 'apple';
|
|
15
24
|
export interface MomentSdkInitOptions {
|
|
16
25
|
/** CSS selector for trigger elements. Default: ".moment-sync-trigger" */
|
|
17
26
|
triggerSelector?: string;
|
|
@@ -42,6 +51,8 @@ export interface MomentSdkInitOptions {
|
|
|
42
51
|
* With a single slug Live syncs that playlist directly; with several it shows
|
|
43
52
|
* a picker. Omit `momentSlug`, `listSlug`, `ids`, `apiSourceId`, and
|
|
44
53
|
* `externalEventId` for this trigger type.
|
|
54
|
+
* - `schedule` → `teamSlug` only. Opens the brand's full schedule experience.
|
|
55
|
+
* Omit every other field (including `calendar`, which is ignored here).
|
|
45
56
|
*/
|
|
46
57
|
export interface MomentSdkOpenPayload {
|
|
47
58
|
teamSlug: string;
|
|
@@ -98,6 +109,14 @@ export interface MomentOAuthStartPayload {
|
|
|
98
109
|
* under a `list`/`team` trigger).
|
|
99
110
|
*/
|
|
100
111
|
subscriptionType?: MomentSubscriptionType;
|
|
112
|
+
/**
|
|
113
|
+
* Present for the `schedule` trigger only: the mixed selection's moment ids.
|
|
114
|
+
* Carried in the popup URL (not localStorage) so the handoff survives browser
|
|
115
|
+
* third-party storage partitioning, matching the single-resource `ids` flow.
|
|
116
|
+
*/
|
|
117
|
+
scheduleMomentIds?: string[];
|
|
118
|
+
/** Present for the `schedule` trigger only: the mixed selection's playlist ids. */
|
|
119
|
+
schedulePlaylistIds?: string[];
|
|
101
120
|
returnOrigin: string;
|
|
102
121
|
}
|
|
103
122
|
/** Parent → Iframe: relay OAuth popup status */
|
|
@@ -110,10 +129,25 @@ export interface MomentOAuthStatusPayload {
|
|
|
110
129
|
export interface MomentResultPayload {
|
|
111
130
|
source: 'moment-live-embed';
|
|
112
131
|
type: 'moment.embed.result';
|
|
113
|
-
|
|
132
|
+
/**
|
|
133
|
+
* `partial` is emitted only by the `schedule` trigger when some — but not all —
|
|
134
|
+
* of a mixed selection subscribed successfully. Inspect `counts` for detail.
|
|
135
|
+
*/
|
|
136
|
+
result: 'success' | 'error' | 'cancelled' | 'partial';
|
|
114
137
|
provider?: CalendarProvider;
|
|
115
138
|
triggerType: MomentTriggerType;
|
|
116
139
|
momentIds: string[];
|
|
140
|
+
/**
|
|
141
|
+
* Aggregate outcome for multi-resource syncs (schedule trigger). `moments` and
|
|
142
|
+
* `playlists` break down the successful count by resource type.
|
|
143
|
+
*/
|
|
144
|
+
counts?: {
|
|
145
|
+
requested: number;
|
|
146
|
+
succeeded: number;
|
|
147
|
+
failed: number;
|
|
148
|
+
moments?: number;
|
|
149
|
+
playlists?: number;
|
|
150
|
+
};
|
|
117
151
|
message?: string;
|
|
118
152
|
}
|
|
119
153
|
/**
|
|
@@ -125,6 +159,17 @@ export interface MomentOpenUrlPayload {
|
|
|
125
159
|
type: 'moment.embed.open.url';
|
|
126
160
|
url: string;
|
|
127
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Iframe → Parent: request to close the modal.
|
|
164
|
+
* Used by interactive embeds (e.g. the `schedule` trigger) whose in-iframe
|
|
165
|
+
* controls — a header close, a "Done" on the success screen — must dismiss the
|
|
166
|
+
* modal. A `cancelled` result is deliberately NOT enough (the SDK keeps the
|
|
167
|
+
* modal open on cancel so the iframe can show retry UI), so this is explicit.
|
|
168
|
+
*/
|
|
169
|
+
export interface MomentClosePayload {
|
|
170
|
+
source: 'moment-live-embed';
|
|
171
|
+
type: 'moment.embed.close';
|
|
172
|
+
}
|
|
128
173
|
export type MomentAnalyticsEventName = 'embed.init' | 'embed.trigger.click' | 'embed.open' | 'oauth.start' | 'oauth.popup.blocked' | 'oauth.popup.closed' | 'subscribe.success' | 'subscribe.error' | 'embed.close';
|
|
129
174
|
export interface MomentAnalyticsEvent {
|
|
130
175
|
event: MomentAnalyticsEventName;
|
|
@@ -134,4 +179,4 @@ export interface MomentAnalyticsEvent {
|
|
|
134
179
|
momentIds?: string[];
|
|
135
180
|
timestamp: number;
|
|
136
181
|
}
|
|
137
|
-
export type MomentPostMessage = MomentOAuthStartPayload | MomentOAuthStatusPayload | MomentResultPayload | MomentOpenUrlPayload;
|
|
182
|
+
export type MomentPostMessage = MomentOAuthStartPayload | MomentOAuthStatusPayload | MomentResultPayload | MomentOpenUrlPayload | MomentClosePayload;
|
package/examples/README.md
CHANGED
|
@@ -23,6 +23,7 @@ Opens **http://localhost:5051** with:
|
|
|
23
23
|
| `/moment/` | `moment` |
|
|
24
24
|
| `/collection/` | `list` |
|
|
25
25
|
| `/brand-schedule/` | `brand-playlist` |
|
|
26
|
+
| `/schedule/` | `schedule` |
|
|
26
27
|
|
|
27
28
|
Demo slugs are in `shared/fixtures.js`. Edit that file if your account uses different values.
|
|
28
29
|
|
package/examples/index.html
CHANGED
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
<h2>Brand schedule</h2>
|
|
30
30
|
<p>Follow the full schedule with <code>triggerType: brand-playlist</code>.</p>
|
|
31
31
|
</a>
|
|
32
|
+
<a class="hub-card" href="/schedule/">
|
|
33
|
+
<h2>Schedule</h2>
|
|
34
|
+
<p>Multi-select collections + events with <code>triggerType: schedule</code>.</p>
|
|
35
|
+
</a>
|
|
32
36
|
</div>
|
|
33
37
|
|
|
34
38
|
<div id="demo-log-host"></div>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Moment SDK — Schedule trigger</title>
|
|
7
|
+
<link rel="stylesheet" href="/shared/demo.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main class="demo-shell">
|
|
11
|
+
<div id="demo-nav" class="demo-top"></div>
|
|
12
|
+
|
|
13
|
+
<h1>Schedule trigger</h1>
|
|
14
|
+
<p class="lead">
|
|
15
|
+
The full brand schedule experience. The fan picks any mix of the brand's collections
|
|
16
|
+
(playlists) and individual events, then syncs them all at once. Only
|
|
17
|
+
<code>data-moment-team-slug</code> and <code>data-moment-trigger-type="schedule"</code>
|
|
18
|
+
are required.
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<article class="hero-card">
|
|
22
|
+
<div class="card-info">
|
|
23
|
+
<div class="card-title">Customize your sdk-demo schedule</div>
|
|
24
|
+
<div class="card-meta">Team slug <code id="team-slug-label"></code></div>
|
|
25
|
+
</div>
|
|
26
|
+
<button type="button" id="schedule-trigger" class="moment-sync-trigger demo-btn">
|
|
27
|
+
View Schedule
|
|
28
|
+
</button>
|
|
29
|
+
</article>
|
|
30
|
+
|
|
31
|
+
<section class="section">
|
|
32
|
+
<h2>Embed snippet</h2>
|
|
33
|
+
<div class="snippet">
|
|
34
|
+
<button class="copy-btn" type="button" data-copy-target="snippet-code">Copy</button>
|
|
35
|
+
<pre id="snippet-code">Loading snippet…</pre>
|
|
36
|
+
</div>
|
|
37
|
+
</section>
|
|
38
|
+
|
|
39
|
+
<div id="demo-log-host"></div>
|
|
40
|
+
</main>
|
|
41
|
+
<script type="module" src="/schedule/main.js"></script>
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DEMO_FIXTURES } from '../shared/fixtures.js';
|
|
2
|
+
import { embedSnippetHtml, initDemoSdk, mountLogPanel, mountNav } from '../shared/boot.js';
|
|
3
|
+
|
|
4
|
+
mountNav({ title: 'Schedule' });
|
|
5
|
+
mountLogPanel();
|
|
6
|
+
|
|
7
|
+
// Allow ?teamSlug=<brand> to point the demo at any brand on the connected
|
|
8
|
+
// backend (handy for local testing); falls back to the sdk-demo fixture.
|
|
9
|
+
const teamSlug =
|
|
10
|
+
new URLSearchParams(window.location.search).get('teamSlug') || DEMO_FIXTURES.teamSlug;
|
|
11
|
+
|
|
12
|
+
document.getElementById('team-slug-label').textContent = teamSlug;
|
|
13
|
+
|
|
14
|
+
const trigger = document.getElementById('schedule-trigger');
|
|
15
|
+
trigger.setAttribute('data-moment-team-slug', teamSlug);
|
|
16
|
+
trigger.setAttribute('data-moment-trigger-type', 'schedule');
|
|
17
|
+
|
|
18
|
+
document.getElementById('snippet-code').textContent = await embedSnippetHtml({
|
|
19
|
+
triggerType: 'schedule',
|
|
20
|
+
label: 'View Schedule',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await initDemoSdk();
|
package/examples/serve.mjs
CHANGED
|
@@ -26,6 +26,8 @@ const ROUTES = {
|
|
|
26
26
|
'/collection/': 'collection/index.html',
|
|
27
27
|
'/brand-schedule': 'brand-schedule/index.html',
|
|
28
28
|
'/brand-schedule/': 'brand-schedule/index.html',
|
|
29
|
+
'/schedule': 'schedule/index.html',
|
|
30
|
+
'/schedule/': 'schedule/index.html',
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
function send(res, status, body, type = 'text/plain; charset=utf-8') {
|
package/package.json
CHANGED