@pugpigbolt/bridge 0.1.2 → 0.1.4

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.
@@ -0,0 +1 @@
1
+ function e(e,t){let{paywall_locked:n,entitlements:r=[]}=e.entry;n&&r.length<=0&&(r=e.feedid?[e.feedid]:[]);let i=e.feedid?t[e.feedid]:!1,a=r.length>0?r.find(e=>t[e]):!0;return!i&&!a}const t={feed:{},rawStories:[],timelines:[],timelinesStates:{},themeURL:[],theme:{},darkTheme:{},timelineMode:`Single`,timelineType:`Timeline`,sourceURL:null,baseUrl:``,metadataLoaded:!1,initialBatchSize:0,filter:{},positionOverrides:[],debugLogs:!1,actionProviderNames:[],authorisationStatus:{},issueAuthorisationStatus:{},read:new Set,saved:new Set,get stories(){return n(this.rawStories,this.saved,this.read,this.authorisationStatus,this.issueAuthorisationStatus)},get themeFileNames(){return this.themeURL?.map(e=>e.split(`/`).pop()??``)??[]},get isSavedTimeline(){return this.timelineType===`SavedTimeline`},get isCollection(){return this.feed?.classes?.includes(`collection_type-edition`)??!1},isTimelineStyleVersion(e){return window.timelineStyleVersion===e}};function n(t,n,a,o,s){let c=!!window.parent?.pugpigBridgeService?.issueAuthorisationStatus;return t.map((t,l)=>(t.locked=c?e(t,s):r(t,o),t.hidden=i(t,o),t.saved=n.has(t.entry.id),t.read=a.has(`${t.feedid}::::${t.entry.id}`),t.originalIndex=l,t))}function r(e,t){let{state:n}=t,{paywall_locked:r=!1}=e.entry;return n!==`active`&&r}function i(e,t){let{state:n}=t,{visibility:r,categories:i,hidden:a}=e.entry,o=i?.find(({scheme:e})=>e===`http://schema.pugpig.com/attributes`);return r?r===`marketing`&&n!==`inactive`||r===`private`&&!!e.locked:o?o.term===`marketing`&&n!==`inactive`||o.term===`private`&&n!==`inactive`:!!a}async function a(e){try{let t=await e;return{data:t?JSON.parse(t):void 0}}catch(e){return{error:e}}}async function o(e,t){let n=window.parent?.pugpigBridgeService,r=n&&n[e],i=t?JSON.stringify(t):void 0;if(r){let{data:t,error:r}=await a(Promise.resolve(i?n[e](i):n[e]()));if(r)throw Error(r instanceof Error?r.message:JSON.stringify(r));return t}window.parent?.postMessage({bolt:`up`,action:e,payload:JSON.parse(i??`{}`)},`*`)}function s(){return(window.location.pathname.split(`/`).pop()?.replace(`.html`,``)??``).split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `)}function c(e,t){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>e.apply(this,r),t)}}const l={openArticle(e,t){return o(`viewArticle`,{...e,story:e,index:t})},openEdition(e,t,n=!1,r=!0){let i={editionId:e,skipTimeline:n,autoDownload:r};return t&&(i.partition=t),o(`openEdition`,i)},shouldViewWidget(e,t){return o(`shouldViewWidget`,{...e,story:e,index:t})},setSaved(e,t,n){return o(`setSaved`,{story:e,index:t,saved:n})},setRead(e,t){return o(`setRead`,{story:e,index:t})},shareStory(e,t){return o(`shareStory`,{story:e,index:t})},addToCalendar(e,t){return o(`addToCalendar`,{story:e,index:t})}},u={logInfo(e,t){if(e===`debug`&&!this.debugLogs)return;let n=this.feed?.id?` [${this.feed.id}]`:``;o(`logInfo`,`[${document.title||`Bolt Timeline`}]${n} ${t}`)},sendAnalytics(e,t,n=0,r={}){let i=s(),a=t?.originalBaseURL||t?.baseURL,o=typeof e==`object`&&e?e:{},c={type:e,story:t,index:n,param:t?.feedid,dimensions:r,...o},l=c.paramName||`pugpigEditionID`,d=this.timelineType===`SavedTimeline`?`/SavedTimeline`:`/Timeline`;return c.dimensions.pugpigEventOrigin=i,!c.paramName&&c?.param===c?.story?.entry.title&&(l=`pugpigPageName`),c?.story?.entry.url&&a&&(c.story.entry.url=new URL(c.story.entry.url,a).href),c?.story?.entry.shareurl&&a&&(c.story.entry.shareurl=new URL(c.story.entry.shareurl,a).href),u.sendAnalyticsEvent(c.type,d,c.param,l,c.dimensions,c.story?.entry,c.story?.feedid,c.index)},sendAnalyticsEvent(e,t,n=``,r=``,i={},a,c,l){let u={name:e,category:t,paramName:r,feedid:c,storyEntry:a,index:l,dimensions:i};return n&&n!==``&&(u.param=typeof n==`string`?n:JSON.stringify(n)),u.dimensions={...i,pugpigEventOrigin:s()},o(`trackAnalyticsEvent`,u)}},d={openAudio(e,t,n){return o(`playAudio`,{story:e,index:n,...t})},openImageGallery(e,t=0){return o(`openImageGallery`,{images:e,initialImage:t})}},f={fireToast(e,t){return o(`fireToast`,{type:e,action:t})},clearForwardTouchesWithin(){return o(`clearForwardTouchesWithin`)},forwardTouchesWithin:c(async function(e){return await this.clearForwardTouchesWithin(),o(`forwardTouchesWithin`,e&&Object.values(e))},300)},p={call(e,t){return o(e,t)},timelineIsReady(){let e=import.meta.env.VITE_GIT_VERSION;o(`timelineIsReady`,{version:e}),this.logInfo(`global`,`timelineIsReady took ${performance.now()}ms`)}},m={...l,...u,...d,...f,...p},h={events:{},debounceTimers:{},$on(e,t){return Array.isArray(this.events[e])||(this.events[e]=[]),this.events[e].push(t),()=>this.removeListener(e,t)},removeListener(e,t){if(Array.isArray(this.events[e])){let n=this.events[e].indexOf(t);n>-1&&this.events[e].splice(n,1)}},$off(e,t){this.removeListener(e,t)},$emit(e,...t){let n=t?.[0]?.debounce,r=Object.keys(this.events).filter(t=>RegExp(`^`+t.replace(/\*/g,`.*`)+`$`).test(e));!r||r.length<=0||r.forEach(e=>{n?(this.debounceTimers[e]&&clearTimeout(this.debounceTimers[e]),this.debounceTimers[e]=setTimeout(()=>{this.events[e].forEach(e=>e.apply(this,t))},n)):this.events[e].forEach(e=>e.apply(this,t))})},$once(e,t){let n=this.$on(e,(...e)=>{n(),t.apply(this,e)})},subscribe(e){return e(this),this.$on(`change`,()=>e(this))}},g=Object.assign(t,{...m,resolveIssueAccess:n=>e(n,t.issueAuthorisationStatus)},h);function _(e){return new Proxy(e,{set(t,n,r,i){let a=Reflect.set(t,n,r,i);return typeof n==`string`?(e.$emit(`change:${n}`,r),e.$emit(`change`,n,r),a):a}})}function v(e,t){if(e.length!==t.length)return!0;for(let n=0;n<e.length;n++){let{entry:{id:r,updated:i}}=e[n],{entry:{id:a,updated:o}}=t[n];if(r!==a||i!==o)return!0}return!1}async function y(e,t){let n=window.parent?.pugpigBridgeService,r=t?await Promise.resolve(n?.[e](t)):await Promise.resolve(n?.[e]()),i=JSON.parse(r),a=window.parent?.pugpig?.timelineHooks?.transforms?.bridge;return a&&(i=await a({method:e,payload:i})),i}const b={stories:async function({updateAction:e}={}){let{stories:t}=await y(`stories`),n=this.rawStories,r=v(n,t);this.rawStories=t,r&&(window.pugpigUpdate(`issueAuthorisationStatus`),window.pugpigUpdate(`timelineInfo`)),n.length>0&&this.$emit(`stories-updated`,{feedUpdated:r,updateAction:e})},timelines:async function(){this.timelines=await y(`timelines`,JSON.stringify(this.filter))},timelineInfo:async function(){let{themeURL:e,timelineType:t,debugLogs:n,sourceURL:r,timelineMode:i,metadata:a,feedReference:o}=await y(`timelineInfo`);this.themeURL=e,this.sourceURL=r,this.theme=a?.configuration?.theme??{},this.darkTheme=a?.configuration?.darkTheme??this.theme,this.feed=o,this.timelineType=t,this.timelineMode=i,a&&(this.metadataLoaded=!0),this.initialBatchSize=a?.configuration?.timeline_initial_batch_size??a?.timeline_initial_batch_size??Math.floor(screen.height/150),window.useTimelineGrid&&(this.positionOverrides=window.timelinePositionOverrides??a?.configuration?.position_overrides??a?.position_overrides??[]),this.debugLogs=n},timeline:async function({editionId:e,progress:t,state:n,downloadError:r}){let i=this.timelines.findIndex(t=>t.id===e);!this.timelines||this.timelines.length<=0||!this.timelines[i]||(this.timelinesStates[e]||(this.timelinesStates[e]={}),this.timelinesStates[e].progress=t,this.timelinesStates[e].state=n,this.timelinesStates[e].downloadError=r)},timelinesInfo:async function(){let{baseurl:e,debugLogs:t,filter:n,css:r=[],theme:i={},darkTheme:a}=await y(`timelinesInfo`);this.theme=i,this.darkTheme=a??i,this.themeURL=r,this.baseUrl=e,this.filter=n,this.debugLogs=t},localeInfo:async function(){let{locale:e=`en-GB`,direction:t=`ltr`}=await y(`localeInfo`);document.documentElement.lang!==e&&(document.documentElement.lang=e?.replace(/_/g,`-`)),document.documentElement.dir!==t&&(document.documentElement.dir=t)},issueAuthorisationStatus:async function(){let e=[...new Set(this.rawStories.flatMap(({feedid:e,entry:t})=>[e,...t.entitlements??[]]))];this.issueAuthorisationStatus=await y(`issueAuthorisationStatus`,JSON.stringify(e))},authorisationStatus:async function(){this.authorisationStatus=await y(`authorisationStatus`)},actionProviderNames:async function(){this.actionProviderNames=await y(`actionProviderNames`)},readStories:async function(){let{readStories:e}=await y(`readStories`);this.read=new Set(e.map(e=>e.join(`::::`)))},savedStories:async function(){let{savedStories:e}=await y(`savedStories`);this.saved=new Set(e.map(e=>e?.[1]))},updateTime:async function(){let{dateTime:e,lastCheckedDateTime:t}=await y(`updateTime`);this.$emit(`time-updated`,{dateTime:e,lastCheckedDateTime:t})},storeGet:async function({key:e}){this.$emit(`store-updated`,{key:e})},webviewInfo:async function(){let{themeURL:e}=await y(`webviewInfo`);this.themeURL=e}};function x(e){return typeof e==`function`&&`__boltHandlers`in e}function S(){if(x(window.pugpigUpdate))return window.pugpigUpdate.__boltHandlers;let e=window.pugpigUpdate?[window.pugpigUpdate]:[],t=Object.assign((t,n={})=>e.forEach(e=>e(t,n)),{__boltHandlers:e});return window.pugpigUpdate=t,e}const C=new WeakMap;function w(e){let t=S();C.has(e)||C.set(e,(t,n={})=>{if(window.parent?.pugpigBridgeService?.[t])return document.querySelectorAll(`iframe`).forEach(e=>{try{e.contentWindow?.pugpigUpdate?.(t,n)}catch{}}),b[t]?.call(e,n);console.info(`Bolt Timeline Bridge: '${t}' not available on this platform.`)});let n=C.get(e);t.includes(n)||t.push(n)}const T=[`localeInfo`,`timelineInfo`,`authorisationStatus`,`savedStories`,`readStories`,`updateTime`,`actionProviderNames`,`stories`];function E(e=T){for(let t of e)window.pugpigUpdate(t)}function D(e,t={}){let{rootSelector:n=`#app`,readyTimeout:r=4e3}=t;`scrollRestoration`in history&&(history.scrollRestoration=`manual`),E();let i=document.querySelector(n),a=[],o=setTimeout(()=>{e.logInfo(`debug`,`${r}ms have passed, forcing timelineIsReady`),e.timelineIsReady()},r);function s(t){a.push(t),[`renderedRequiredStyles`,...e.themeFileNames??[]].every(e=>a.includes(e))&&(clearTimeout(o),document.fonts.ready.then(()=>e.timelineIsReady()))}for(let t of e.themeURL??[]){let e=document.createElement(`link`);e.rel=`stylesheet`,e.href=t,e.onload=()=>s(t),e.onerror=()=>s(t),document.head.appendChild(e)}s(`renderedRequiredStyles`);function c(){let t=[...e.feed?.classes??[]];e.isSavedTimeline&&t.push(`saved-timeline`),i&&t.length&&i.classList.add(...t)}c();let l=e.$on(`change:feed`,c);return()=>{clearTimeout(o),l()}}export{_ as a,w as i,D as n,g as o,E as r,m as s,T as t};
@@ -0,0 +1 @@
1
+ import{n as e,r as t}from"./mockBridgeService-M_48ewQL.mjs";function n(e){return e===`origin`&&(e=window.location.origin),/^https?:\/\//i.test(e)||(e=`https://`+e),e}async function r(e){let n=`${e.live_domain}/timelines.json`,r=await t(n),i=r.timelines?.find(t=>t.id===e.edition)??r.timelines[0],a=new URL(i?.feed??``,n).href,o=await t(e?.stories||a);return Object.assign(e,{live_domain:e.live_domain,timelines:r.timelines,timeline:i,stories:o.stories,metadata:o.metadata})}async function i(e){let n=e.platform??`web`,r=(await t(`${e.live_domain}/bolt_properties.json`))?.[`${e.config}_version`]??`latest_version`,i=`${e.live_domain}/bolt/config/${r}/web/config.json`,a=(e.config?await t(i):void 0)?.timeline_css.map(t=>`${e.live_domain}/bolt/config/${r}/${n}/${t}`);return Object.assign(e,{themeUrl:a})}async function a(e,t){let a=new URLSearchParams(window.location.search),o=Object.fromEntries(a.entries());if(o.useStorage){let e=localStorage.getItem(`config`);if(e)return JSON.parse(e)}let s={...window.__TIMELINE_LOCAL_CONFIG__,...o,debugLogs:o.debugLogs===`true`};return s.live_domain&&(s.live_domain=n(s.live_domain),s=await r(s),s=await i(s)),localStorage.setItem(`config`,JSON.stringify(s)),s}async function o(t,n){window.config=await a(t,n),window.pugpigBridgeService=e(window.config)}export{o as injectBridge};
@@ -1,12 +1,6 @@
1
1
  import { App } from "vue";
2
2
 
3
3
  //#region src/types.d.ts
4
- /**
5
- * @section Story types
6
- *
7
- * Core data structures for stories and their entries, shared across the bridge
8
- * and widget layer.
9
- */
10
4
  interface StoryImage {
11
5
  src?: string;
12
6
  srcset?: string;
@@ -15,7 +9,6 @@ interface StoryImage {
15
9
  credit?: string;
16
10
  externalurl?: string;
17
11
  }
18
- /** A single DFP ad size — either a [width, height] tuple or the string 'fluid'. */
19
12
  type AdSize = number[] | string;
20
13
  interface StoryEntry {
21
14
  id: string;
@@ -155,62 +148,29 @@ interface Story {
155
148
  isArticleLink?: boolean;
156
149
  original_hidden?: boolean;
157
150
  }
158
- /**
159
- * @section Bridge service interface
160
- *
161
- * The `BridgeService` interface consumed by `@pugpigbolt/components` and widgets to
162
- * open articles, send analytics, manage saved state, and communicate with the
163
- * native shell via events.
164
- */
165
151
  interface BridgeService {
166
- /** Current timeline type string, e.g. 'Timeline' | 'SavedTimeline'. */
167
152
  timelineType: string;
168
- /** Processed story array — locked, hidden, saved, and read flags applied. */
169
153
  stories: Story[];
170
- /** Unprocessed stories as received from the native shell before flag processing. */
171
154
  rawStories: Story[];
172
- /** Number of stories rendered in the first batch. */
173
155
  initialBatchSize: number;
174
- /** Position override rules applied to story ordering. */
175
156
  positionOverrides: unknown[];
176
- /** Names of registered native action providers, if any. */
177
157
  actionProviderNames?: string[];
178
- /** Current user authorisation status — state ('active'|'inactive'), token, userInfo. */
179
158
  authorisationStatus?: AuthorisationStatus;
180
- /** The source url that loaded the timeline. */
181
159
  sourceURL?: string;
182
- /** Returns true if the native app is running at least the given timeline style version. */
183
160
  isTimelineStyleVersion(version: string): boolean;
184
- /** Sends an analytics event. event is a string action name or a Record with type/param. story and index identify the context. */
185
161
  sendAnalytics(event: string | Record<string, unknown>, story?: Story, index?: number, extra?: Record<string, unknown>): void;
186
- /** Asks the native shell to open a story in the native article reader. */
187
162
  openArticle(story: Story, index: number): void;
188
- /** Marks a story as read in the native shell's read-state store. */
189
163
  setRead(story: Story, index: number): void;
190
- /** Opens the native audio player for a story. metadata carries player-specific config. */
191
164
  openAudio(story: Story, metadata: Record<string, unknown>, index: number): void;
192
- /** Opens the native share sheet for a story. */
193
165
  shareStory(story: Story, index: number): void;
194
- /** Saves or unsaves a story in the native shell's saved-stories store. */
195
166
  setSaved(story: Story, index: number, saved: boolean): void;
196
- /** Displays a native toast notification. key is the toast identifier, action is an optional CTA. */
197
167
  fireToast(key: string, action?: string): void;
198
- /** Returns whether the story is locked based on issue authorisation status. */
199
168
  resolveIssueAccess(story: Pick<Story, 'feedid' | 'entry'>): boolean;
200
- /** Adds an event-type story to the device calendar. */
201
169
  addToCalendar(story: Story, index: number): void;
202
- /** Dispatches an arbitrary named action to the native bridge. Use as an escape hatch when no typed method exists. */
203
170
  call(method: string, ...args: unknown[]): Promise<unknown>;
204
- /** Subscribes to a bridge event by name. Returns an unsubscribe function — call it in onUnmounted. */
205
171
  $on(event: string, callback: (...args: unknown[]) => void): () => void;
206
- /** Emits a named bridge event with an optional data payload. */
207
172
  $emit(event: string, data?: unknown): void;
208
173
  }
209
- /**
210
- * @section Timeline types
211
- *
212
- * Types describing timelines and their download or progress state.
213
- */
214
174
  interface Timeline {
215
175
  id: string;
216
176
  feed?: string;
@@ -221,12 +181,6 @@ interface TimelineState {
221
181
  state?: 'idle' | 'downloading' | 'deleting' | (string & {});
222
182
  downloadError?: string;
223
183
  }
224
- /**
225
- * @section Feed, auth, and theme
226
- *
227
- * Feed descriptor, authorisation status, issue-level access map, and theme
228
- * types used across the bridge.
229
- */
230
184
  interface Feed {
231
185
  id?: string;
232
186
  classes?: string[];
@@ -242,12 +196,6 @@ interface AuthorisationStatus {
242
196
  }
243
197
  type IssueAuthorisationStatus = Record<string, boolean>;
244
198
  type Theme = Record<string, unknown>;
245
- /**
246
- * @section Remote config
247
- *
248
- * Timeline metadata and the full remote configuration shape delivered by the
249
- * native shell at startup.
250
- */
251
199
  interface TimelineMetadata {
252
200
  configuration?: {
253
201
  theme?: Theme;
@@ -275,15 +223,9 @@ interface RemoteConfig {
275
223
  config?: string;
276
224
  globalDir?: string;
277
225
  baseurl?: string;
226
+ version?: string;
278
227
  [key: string]: unknown;
279
228
  }
280
- /**
281
- * @section Native bridge service interface
282
- *
283
- * The raw `parent.pugpigBridgeService` interface exposed by the native iOS and
284
- * Android shell. Widget code should not call this directly — use `BridgeService`
285
- * instead.
286
- */
287
229
  interface PugpigBridgeService {
288
230
  stories(payload?: string): unknown;
289
231
  timeline(payload?: string): unknown;
@@ -317,12 +259,6 @@ interface PugpigBridgeService {
317
259
  actionProviderNames?(payload?: string): unknown;
318
260
  [key: string]: ((payload?: string) => unknown) | undefined;
319
261
  }
320
- /**
321
- * @section Incoming handler payload types
322
- *
323
- * Payload shapes for each `pugpigUpdate` method call received from the native
324
- * shell.
325
- */
326
262
  interface StoriesPayload {
327
263
  stories: Story[];
328
264
  }
@@ -366,37 +302,11 @@ interface TimelineInfoPayload {
366
302
  feedReference: Feed;
367
303
  metadata: TimelineMetadata;
368
304
  }
369
- /**
370
- * @section Bootstrap
371
- *
372
- * Options passed to the bridge bootstrap process that initialises the Vue app
373
- * and connects it to the native shell.
374
- */
375
305
  interface BootstrapOptions {
376
- /**
377
- * CSS selector for the app root element used to apply feed classes.
378
- * Defaults to `'#app'`.
379
- */
380
306
  rootSelector?: string;
381
- /**
382
- * Milliseconds to wait for theme CSS and fonts before forcing `timelineIsReady`.
383
- * Defaults to `4000`.
384
- */
385
307
  readyTimeout?: number;
386
308
  }
387
- /**
388
- * @section Plugin type
389
- *
390
- * Type alias for the Vue plugin produced by the bridge factory. Pass an
391
- * instance to `app.use()` to install the bridge into a Vue application.
392
- */
393
309
  type BoltBridgePlugin = (app: App, callback?: () => void) => void;
394
- /**
395
- * @section Analytics
396
- *
397
- * Context and dimension types passed when firing analytics events through the
398
- * bridge.
399
- */
400
310
  interface AnalyticsContext {
401
311
  debugLogs: boolean;
402
312
  feed?: {
@@ -408,12 +318,6 @@ interface AnalyticsDimensions {
408
318
  pugpigEventOrigin?: string;
409
319
  [key: string]: unknown;
410
320
  }
411
- /**
412
- * @section Global window augmentation
413
- *
414
- * Extensions added to the browser `window` object by the native shell,
415
- * including the `pugpigUpdate` entry point and timeline configuration globals.
416
- */
417
321
  declare global {
418
322
  interface Window {
419
323
  boltBridge?: unknown;
@@ -549,8 +453,8 @@ declare function createPugpigBridgeService(config: BridgeServiceConfig): {
549
453
  ok: boolean;
550
454
  };
551
455
  css: string[] | undefined;
552
- theme: Theme | undefined;
553
- darkTheme: Theme | undefined;
456
+ theme: Record<string, unknown> | undefined;
457
+ darkTheme: Record<string, unknown> | undefined;
554
458
  debugLogs: boolean | undefined;
555
459
  };
556
460
  storeGet(payload?: string): string | null;
@@ -587,15 +491,7 @@ declare const BOOTSTRAP_METHODS: readonly ["localeInfo", "timelineInfo", "author
587
491
  declare function triggerPugpigUpdateSequence(methods?: readonly string[]): void;
588
492
  //#endregion
589
493
  //#region src/index.d.ts
590
- /**
591
- * @signature globalBridge: GlobalBridge
592
- * @description Singleton proxy bridge instance exposed as window.boltBridge. Emits change:prop events on every property write. Use this for non-Vue integrations; Vue apps should use useBridge() instead.
593
- */
594
494
  declare const globalBridge: GlobalBridge;
595
- /**
596
- * @signature bootstrap(options?: BootstrapOptions): () => void
597
- * @description Initialises the bridge for a non-Vue framework entry point. Call once before rendering. Returns a teardown function that removes all listeners and cleans up state.
598
- */
599
495
  declare function bootstrap(options?: BootstrapOptions): () => void;
600
496
  //#endregion
601
497
  export { TimelineState as A, StoryEntry as C, TimelineArgs as D, Timeline as E, UpdateTimePayload as M, WebviewInfoPayload as N, TimelineInfoPayload as O, Story as S, Theme as T, PugpigBridgeService as _, createPugpigBridgeService as a, SavedStoriesPayload as b, AnalyticsContext as c, BoltBridgePlugin as d, BootstrapOptions as f, LocaleInfoPayload as g, IssueAuthorisationStatus as h, triggerPugpigUpdateSequence as i, TimelinesInfoPayload as j, TimelineMetadata as k, AnalyticsDimensions as l, Feed as m, globalBridge as n, GlobalBridge as o, BridgeService as p, BOOTSTRAP_METHODS as r, AdSize as s, bootstrap as t, AuthorisationStatus as u, ReadStoriesPayload as v, StoryImage as w, StoriesPayload as x, RemoteConfig as y };
@@ -0,0 +1,27 @@
1
+ async function e(e,t){let n=await(await fetch(e)).json();return t?n?.[t]:n}function t(e){let t=0,n=`idle`;window[e]&&clearInterval(window[e]),window[e]=setInterval(()=>{let r=t<=0,i=t>=1;n=r||i?`idle`:`downloading`,i&&clearInterval(window[e]),window.pugpigUpdate(`timeline`,{editionId:e,state:n,progress:t}),t+=.05},500)}let n=null;function r(){return n||(n=document.createElement(`div`),n.id=`bridge-toast-container`,n.style.cssText=`
2
+ position: fixed;
3
+ top: 10px;
4
+ right: 10px;
5
+ z-index: 99999;
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: 8px;
9
+ pointer-events: none;
10
+ `,document.body.appendChild(n),n)}function i(e,t=3e3){let n=r(),i=document.createElement(`div`);i.style.cssText=`
11
+ background: #333;
12
+ color: #fff;
13
+ padding: 8px 12px;
14
+ border-radius: 4px;
15
+ font-size: 12px;
16
+ font-family: monospace;
17
+ max-width: 300px;
18
+ word-break: break-word;
19
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
20
+ animation: slideIn 0.3s ease;
21
+ pointer-events: auto;
22
+ `,i.textContent=e;let a=document.createElement(`style`);a.textContent=`
23
+ @keyframes slideIn {
24
+ from { transform: translateX(100%); opacity: 0; }
25
+ to { transform: translateX(0); opacity: 1; }
26
+ }
27
+ `,document.querySelector(`#bridge-toast-styles`)||(a.id=`bridge-toast-styles`,document.head.appendChild(a)),n.appendChild(i),setTimeout(()=>{i.style.opacity=`0`,i.style.transition=`opacity 0.3s ease`,setTimeout(()=>i.remove(),300)},t)}function a(e){return{stories(){return window.stories=window.stories||o(e,e.stories),{stories:window.stories}},timeline(){return{progress:0,state:`idle`}},timelines(){return window.timelines=window.timelines||e.timelines,window.timelines},logInfo(e){return console.info(JSON.parse(e??`null`))},timelineInfo(){let{themeUrl:t,timelineType:n,metadata:r,debugLogs:i}=window.config??e,a=(window.config??e).timeline;return{themeURL:t,timelineType:n,debugLogs:i,metadata:r,sourceURL:window.sourceURL||window.location.href,feedReference:a}},forwardTouchesWithin(e){JSON.parse(e??`[]`).forEach(e=>{let t=document.createElement(`div`);t.className=`forwardTouchesWithin`,t.style.position=`absolute`,t.style.width=e.w+`px`,t.style.height=e.h+`px`,t.style.top=e.y+`px`,t.style.left=e.x+`px`,t.style.outline=`3px solid red`,t.style.pointerEvents=`none`,document.body.appendChild(t)})},clearForwardTouchesWithin(){document.querySelectorAll(`.forwardTouchesWithin`).forEach(e=>e.remove())},issueAuthorisationStatus(e){return JSON.parse(e??`[]`).reduce((e,t)=>(e[t]=!0,e),{})},shouldViewWidget(e){return!0},viewArticle(){console.log(`eheheh`)},startEditionDownload(e){let{editionId:n}=JSON.parse(e??`{}`);t(n)},deleteEdition(e){let{editionId:t}=JSON.parse(e??`{}`);window.pugpigUpdate(`timeline`,{editionId:t,state:`deleting`}),setTimeout(()=>window.pugpigUpdate(`timeline`,{editionId:t,state:`idle`,progress:0}),1e3)},cancelEditionDownload(e){let{editionId:t}=JSON.parse(e??`{}`);window[t]&&clearInterval(window[t]),window.pugpigUpdate(`timeline`,{editionId:t,state:`idle`,progress:0})},timelinesInfo(){let{theme:t,darkTheme:n,css:r,debugLogs:i,live_domain:a}=window.config??e;return{baseurl:a,filter:{ok:!0},css:r,theme:t,darkTheme:n,debugLogs:i}},storeGet(e){let t=JSON.parse(e??`null`);return localStorage.getItem(t)},storeSet(e){let{key:t,value:n}=JSON.parse(e??`{}`),r=localStorage.setItem(t,n);return setTimeout(()=>window.pugpigUpdate(`storeGet`,{key:t}),200),r},localeInfo(){let{globalDir:t}=window.config??e;return{locale:`en_GB`,direction:t}},trackAnalyticsEvent(e){return JSON.parse(e??`null`)},updateTime(){return{dateTime:Date.now(),lastCheckedDateTime:Date.now()}},authorisationStatus(e){return{state:`active`,userInfo:{}}},savedStories(){return{savedStories:window.saved||[]}},readStories(){return{readStories:window.read||[]}},localizableString(e){let{stringId:t,params:n}=JSON.parse(e??`{}`);return n?.length?`${t}(${n.join(`, `)})`:t},localizableRelativeTimeString(e){let{datetime:t}=JSON.parse(e??`{}`);return t?new Date(t).toLocaleDateString():null},localizableDateTimeString(e){let{datetime:t}=JSON.parse(e??`{}`);return t?new Date(t).toLocaleString():null},localizableQuantityString(e){let{stringId:t,quantity:n}=JSON.parse(e??`{}`);return`${t}(${n})`},setSaved(e){let{story:t,saved:n}=JSON.parse(e??`{}`);window.saved=window.saved||[];let r=window.saved.findIndex(([e,n])=>e===t.feedid&&n===t.entry.id);!n&&r>-1&&window.saved.splice(r,1),n&&window.saved.push([t.feedid,t.entry.id]),window.pugpigUpdate(`savedStories`)},setRead(e){let{story:t}=JSON.parse(e??`{}`);return window.read=window.read||[],window.read.push([t.feedid,t.entry.id]),window.pugpigUpdate(`readStories`),JSON.parse(e??`null`)}}}function o(e,t){return t.map(t=>({feedid:e?.timeline?.id||`Bolt Timeline`,entry:t,saved:!1,locked:!0,baseURL:e.live_domain}))}function s(e){let t=a(e);return new Proxy(t,{get(t,n){let r=t[n];return typeof r==`function`?(...t)=>{let a=r(t[0])??null;return e.debugLogs&&a&&(i(`${n} ${JSON.stringify(t)}`),console.log(`%c[Pugpig Bridge Service]%c[${n}]`,`color: green;`,`color: orange;`,n,r(t[0]))),a?JSON.stringify(a):void 0}:t[n]}})}export{s as n,e as r,a as t};
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as TimelineState, C as StoryEntry, D as TimelineArgs, E as Timeline, M as UpdateTimePayload, N as WebviewInfoPayload, O as TimelineInfoPayload, S as Story, T as Theme, _ as PugpigBridgeService, a as createPugpigBridgeService, b as SavedStoriesPayload, c as AnalyticsContext, d as BoltBridgePlugin, f as BootstrapOptions, g as LocaleInfoPayload, h as IssueAuthorisationStatus, i as triggerPugpigUpdateSequence, j as TimelinesInfoPayload, k as TimelineMetadata, l as AnalyticsDimensions, m as Feed, n as globalBridge, o as GlobalBridge, p as BridgeService, r as BOOTSTRAP_METHODS, s as AdSize, t as bootstrap, u as AuthorisationStatus, v as ReadStoriesPayload, w as StoryImage, x as StoriesPayload, y as RemoteConfig } from "./index-BjDhZ_q5.mjs";
1
+ import { A as TimelineState, C as StoryEntry, D as TimelineArgs, E as Timeline, M as UpdateTimePayload, N as WebviewInfoPayload, O as TimelineInfoPayload, S as Story, T as Theme, _ as PugpigBridgeService, a as createPugpigBridgeService, b as SavedStoriesPayload, c as AnalyticsContext, d as BoltBridgePlugin, f as BootstrapOptions, g as LocaleInfoPayload, h as IssueAuthorisationStatus, i as triggerPugpigUpdateSequence, j as TimelinesInfoPayload, k as TimelineMetadata, l as AnalyticsDimensions, m as Feed, n as globalBridge, o as GlobalBridge, p as BridgeService, r as BOOTSTRAP_METHODS, s as AdSize, t as bootstrap, u as AuthorisationStatus, v as ReadStoriesPayload, w as StoryImage, x as StoriesPayload, y as RemoteConfig } from "./chunks/index-BWVJLr0k.mjs";
2
2
  export { AdSize, AnalyticsContext, AnalyticsDimensions, AuthorisationStatus, BOOTSTRAP_METHODS, BoltBridgePlugin, BootstrapOptions, BridgeService, Feed, GlobalBridge, IssueAuthorisationStatus, LocaleInfoPayload, PugpigBridgeService, ReadStoriesPayload, RemoteConfig, SavedStoriesPayload, StoriesPayload, Story, StoryEntry, StoryImage, Theme, Timeline, TimelineArgs, TimelineInfoPayload, TimelineMetadata, TimelineState, TimelinesInfoPayload, UpdateTimePayload, WebviewInfoPayload, bootstrap, createPugpigBridgeService, globalBridge, triggerPugpigUpdateSequence };
package/dist/index.mjs CHANGED
@@ -1,19 +1 @@
1
- import { a as makeProxyBridge, i as setupPugpigUpdate, n as bootstrap$1, o as rawBridge, r as triggerPugpigUpdateSequence, t as BOOTSTRAP_METHODS } from "./bootstrap-DQyoNFue.mjs";
2
- import { t as createPugpigBridgeService } from "./mockBridgeService-vnaAIjCu.mjs";
3
- //#region src/index.ts
4
- /**
5
- * @signature globalBridge: GlobalBridge
6
- * @description Singleton proxy bridge instance exposed as window.boltBridge. Emits change:prop events on every property write. Use this for non-Vue integrations; Vue apps should use useBridge() instead.
7
- */
8
- const globalBridge = window.boltBridge || makeProxyBridge(rawBridge);
9
- window.boltBridge = globalBridge;
10
- setupPugpigUpdate(globalBridge);
11
- /**
12
- * @signature bootstrap(options?: BootstrapOptions): () => void
13
- * @description Initialises the bridge for a non-Vue framework entry point. Call once before rendering. Returns a teardown function that removes all listeners and cleans up state.
14
- */
15
- function bootstrap(options) {
16
- return bootstrap$1(globalBridge, options);
17
- }
18
- //#endregion
19
- export { BOOTSTRAP_METHODS, bootstrap, createPugpigBridgeService, globalBridge, triggerPugpigUpdateSequence };
1
+ import{a as e,i as t,n,o as r,r as i,t as a}from"./chunks/bootstrap-B7Gwb_jl.mjs";import{t as o}from"./chunks/mockBridgeService-M_48ewQL.mjs";const s=window.boltBridge||e(r);window.boltBridge=s,t(s);function c(e){return n(s,e)}export{a as BOOTSTRAP_METHODS,c as bootstrap,o as createPugpigBridgeService,s as globalBridge,i as triggerPugpigUpdateSequence};
package/dist/vue.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as TimelineState, C as StoryEntry, D as TimelineArgs, E as Timeline, M as UpdateTimePayload, N as WebviewInfoPayload, O as TimelineInfoPayload, S as Story, T as Theme, _ as PugpigBridgeService, b as SavedStoriesPayload, c as AnalyticsContext, d as BoltBridgePlugin, f as BootstrapOptions, g as LocaleInfoPayload, h as IssueAuthorisationStatus, i as triggerPugpigUpdateSequence, j as TimelinesInfoPayload, k as TimelineMetadata, l as AnalyticsDimensions, m as Feed, o as GlobalBridge, p as BridgeService, r as BOOTSTRAP_METHODS, s as AdSize, u as AuthorisationStatus, v as ReadStoriesPayload, w as StoryImage, x as StoriesPayload, y as RemoteConfig } from "./index-BjDhZ_q5.mjs";
1
+ import { A as TimelineState, C as StoryEntry, D as TimelineArgs, E as Timeline, M as UpdateTimePayload, N as WebviewInfoPayload, O as TimelineInfoPayload, S as Story, T as Theme, _ as PugpigBridgeService, b as SavedStoriesPayload, c as AnalyticsContext, d as BoltBridgePlugin, f as BootstrapOptions, g as LocaleInfoPayload, h as IssueAuthorisationStatus, i as triggerPugpigUpdateSequence, j as TimelinesInfoPayload, k as TimelineMetadata, l as AnalyticsDimensions, m as Feed, o as GlobalBridge, p as BridgeService, r as BOOTSTRAP_METHODS, s as AdSize, u as AuthorisationStatus, v as ReadStoriesPayload, w as StoryImage, x as StoriesPayload, y as RemoteConfig } from "./chunks/index-BWVJLr0k.mjs";
2
2
  import * as _$vue from "vue";
3
3
  import { App } from "vue";
4
4
 
@@ -13,43 +13,6 @@ declare function registerToolTips({
13
13
  }?: RegisterTooltipsOptions): void;
14
14
  //#endregion
15
15
  //#region src/vue/useBridge.d.ts
16
- /**
17
- * @description Returns the reactive bridge service with all store data as refs and all outgoing action methods bound to the reactive store.
18
- * @usage const { stories, authorisationStatus, openArticle, sendAnalytics, ... } = useBridge()
19
- * @returns stories {Ref<Story[]>} Derived story array with locked, hidden, saved, and read flags applied
20
- * @returns rawStories {Ref<Story[]>} Unprocessed raw stories as received from the bridge
21
- * @returns feed {Ref<Feed>} Current feed metadata
22
- * @returns timelines {Ref<Timeline[]>} Available timelines
23
- * @returns timelinesStates {Ref<Record<string, TimelineState>>} Per-timeline state records
24
- * @returns themeURL {Ref<string[]>} URLs for the active theme files
25
- * @returns theme {Ref<Theme>} Active light theme data
26
- * @returns darkTheme {Ref<Theme>} Active dark theme data
27
- * @returns timelineMode {Ref<string>} Current timeline display mode
28
- * @returns timelineType {Ref<string>} Current timeline type (e.g. 'Timeline' | 'SavedTimeline')
29
- * @returns sourceURL {Ref<string | null>} Source URL of the current feed
30
- * @returns baseUrl {Ref<string>} Base URL used to resolve relative paths
31
- * @returns metadataLoaded {Ref<boolean>} Whether feed metadata has been loaded
32
- * @returns initialBatchSize {Ref<number>} Number of stories in the first rendered batch
33
- * @returns filter {Ref<Record<string, unknown>>} Active filter state
34
- * @returns authorisationStatus {Ref<AuthorisationStatus>} Current user authorisation status
35
- * @returns issueAuthorisationStatus {Ref<IssueAuthorisationStatus>} Per-issue authorisation status
36
- * @returns openArticle {(story: Story, index: number) => Promise<unknown>} Opens a story in the native reader
37
- * @returns shareStory {(story: Story, index: number) => Promise<unknown>} Triggers the native share sheet
38
- * @returns setSaved {(story: Story, index: number, saved: boolean) => Promise<unknown>} Updates the saved state for a story
39
- * @returns setRead {(story: Story, index: number) => Promise<unknown>} Marks a story as read
40
- * @returns sendAnalytics {(type: string | Record<string, unknown>, story: Story, index?: number, dimensions?: AnalyticsDimensions) => Promise<unknown>} Sends an analytics event
41
- * @returns call {(action: string, payload?: unknown) => Promise<unknown>} Dispatches an arbitrary bridge action
42
- * @returns timelineIsReady {() => void} Signals that the Vue app has mounted
43
- * @returns fireToast {(type: string, action: string) => Promise<unknown>} Displays a native toast notification
44
- * @returns openAudio {(story: Story, fallback: Record<string, unknown>, index: number) => Promise<unknown>} Opens the native audio player
45
- * @returns openImageGallery {(images: StoryImage[] | undefined, initialImage?: number) => Promise<unknown>} Opens the native image gallery
46
- * @returns addToCalendar {(story: Story, index: number) => Promise<unknown>} Adds an event to the device calendar
47
- * @returns resolveIssueAccess {(story: Story) => boolean} Resolves whether a story is locked based on issue authorisation
48
- * @returns $on {(event: string, listener: (...args: unknown[]) => void) => () => void} Subscribes to a bridge event; returns an unsubscribe function
49
- * @returns $emit {(event: string, ...args: unknown[]) => void} Emits a bridge event
50
- * @returns $once {(event: string, listener: (...args: unknown[]) => void) => void} Subscribes to a bridge event for a single invocation
51
- * @returns bridge {BridgeService} The raw reactive bridge instance for direct access
52
- */
53
16
  declare function useBridge(): {
54
17
  resolveIssueAccess: (story: Story) => boolean;
55
18
  $on: (event: string, listener: (...args: unknown[]) => void) => () => void;
@@ -77,28 +40,28 @@ declare function useBridge(): {
77
40
  setRead(story: Story, index: number): Promise<unknown>;
78
41
  shareStory(story: Story, index: number): Promise<unknown>;
79
42
  addToCalendar(story: Story, index: number): Promise<unknown>;
43
+ authorisationStatus: _$vue.Ref<AuthorisationStatus, AuthorisationStatus>;
44
+ actionProviderNames: _$vue.Ref<string[], string[]>;
45
+ stories: _$vue.Ref<Story[], Story[]>;
46
+ timelineType: _$vue.Ref<string, string>;
47
+ debugLogs: _$vue.Ref<boolean, boolean>;
48
+ theme: _$vue.Ref<Theme, Theme>;
49
+ darkTheme: _$vue.Ref<Theme, Theme>;
80
50
  filter: _$vue.Ref<Record<string, unknown>, Record<string, unknown>>;
81
51
  feed: _$vue.Ref<Feed, Feed>;
82
52
  rawStories: _$vue.Ref<Story[], Story[]>;
83
53
  timelines: _$vue.Ref<Timeline[], Timeline[]>;
84
54
  timelinesStates: _$vue.Ref<Record<string, TimelineState>, Record<string, TimelineState>>;
85
55
  themeURL: _$vue.Ref<string[], string[]>;
86
- theme: _$vue.Ref<Theme, Theme>;
87
- darkTheme: _$vue.Ref<Theme, Theme>;
88
56
  timelineMode: _$vue.Ref<string, string>;
89
- timelineType: _$vue.Ref<string, string>;
90
57
  sourceURL: _$vue.Ref<string | null, string | null>;
91
58
  baseUrl: _$vue.Ref<string, string>;
92
59
  metadataLoaded: _$vue.Ref<boolean, boolean>;
93
60
  initialBatchSize: _$vue.Ref<number, number>;
94
61
  positionOverrides: _$vue.Ref<unknown[], unknown[]>;
95
- debugLogs: _$vue.Ref<boolean, boolean>;
96
- actionProviderNames: _$vue.Ref<string[], string[]>;
97
- authorisationStatus: _$vue.Ref<AuthorisationStatus, AuthorisationStatus>;
98
62
  issueAuthorisationStatus: _$vue.Ref<IssueAuthorisationStatus, IssueAuthorisationStatus>;
99
63
  read: _$vue.Ref<Set<string>, Set<string>>;
100
64
  saved: _$vue.Ref<Set<string>, Set<string>>;
101
- stories: _$vue.Ref<Story[], Story[]>;
102
65
  themeFileNames: _$vue.Ref<string[], string[]>;
103
66
  isSavedTimeline: _$vue.Ref<boolean, boolean>;
104
67
  isCollection: _$vue.Ref<boolean, boolean>;
package/dist/vue.mjs CHANGED
@@ -1,213 +1 @@
1
- import { i as setupPugpigUpdate, o as rawBridge, r as triggerPugpigUpdateSequence, s as outgoingActions, t as BOOTSTRAP_METHODS } from "./bootstrap-DQyoNFue.mjs";
2
- import { onUnmounted, reactive, ref, toRefs, watchEffect } from "vue";
3
- //#region src/vue/tooltip.ts
4
- const tooltipsQueue = ref([]);
5
- const tooltipsRequests = ref([]);
6
- const isScrolling = ref(false);
7
- const createIntersectionObserver = (id, element) => {
8
- if (tooltipsRequests.value.includes(id)) return;
9
- const observer = new IntersectionObserver(([entry]) => {
10
- if (!entry.isIntersecting) return;
11
- if (tooltipsRequests.value.includes(id)) return observer.disconnect();
12
- tooltipsQueue.value.push({
13
- id,
14
- el: element
15
- });
16
- observer.disconnect();
17
- }, { threshold: 1 });
18
- observer.observe(element);
19
- return observer;
20
- };
21
- function initToolTips() {
22
- window.addEventListener("scroll", () => {
23
- isScrolling.value = true;
24
- window.clearTimeout(window.scrollTimeout);
25
- window.scrollTimeout = setTimeout(() => isScrolling.value = false, 150);
26
- }, false);
27
- watchEffect(() => {
28
- if (isScrolling.value) return;
29
- tooltipsQueue.value.forEach((tooltip, index) => {
30
- if (tooltipsRequests.value.includes(tooltip.id)) return;
31
- const { x, y, width: w, height: h } = tooltip.el.getBoundingClientRect();
32
- const source = {
33
- w,
34
- h,
35
- x,
36
- y: y + window.scrollY
37
- };
38
- window.parent?.pugpigBridgeService?.showToolTip?.(JSON.stringify({
39
- id: tooltip.id,
40
- source
41
- }));
42
- tooltipsQueue.value.splice(index, 1);
43
- tooltipsRequests.value.push(tooltip.id);
44
- });
45
- });
46
- }
47
- function registerToolTips({ attribute = "tooltip", observerOptions } = {}) {
48
- if (!window.parent?.pugpigBridgeService?.showToolTip) return;
49
- initToolTips();
50
- const mutationObserverCallback = (mutations) => {
51
- mutations.forEach((mutation) => mutation.addedNodes.forEach((node) => {
52
- if (node.nodeType !== Node.ELEMENT_NODE) return;
53
- const el = node;
54
- const tooltipValue = el.getAttribute(attribute);
55
- if (!tooltipValue) return;
56
- if (tooltipsRequests.value.includes(tooltipValue)) return;
57
- createIntersectionObserver(tooltipValue, el);
58
- }));
59
- };
60
- const mutationObserver = new MutationObserver(mutationObserverCallback);
61
- mutationObserverCallback([{ addedNodes: document.body.childNodes }]);
62
- mutationObserver.observe(document.body, observerOptions ?? {
63
- childList: true,
64
- subtree: true
65
- });
66
- }
67
- function installTooltipDirective(app) {
68
- if (!window.parent?.pugpigBridgeService?.showToolTip) {
69
- app.directive("tooltip", {});
70
- return;
71
- }
72
- initToolTips();
73
- app.directive("tooltip", { mounted: (el, binding) => createIntersectionObserver(binding.value, el) });
74
- }
75
- //#endregion
76
- //#region src/vue/useBridge.ts
77
- const reactiveBridge = window.boltBridge || reactive(rawBridge);
78
- function bindAll(obj, ctx) {
79
- return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, typeof v === "function" ? v.bind(ctx) : v]));
80
- }
81
- const boundActions = bindAll(outgoingActions, reactiveBridge);
82
- /**
83
- * @description Returns the reactive bridge service with all store data as refs and all outgoing action methods bound to the reactive store.
84
- * @usage const { stories, authorisationStatus, openArticle, sendAnalytics, ... } = useBridge()
85
- * @returns stories {Ref<Story[]>} Derived story array with locked, hidden, saved, and read flags applied
86
- * @returns rawStories {Ref<Story[]>} Unprocessed raw stories as received from the bridge
87
- * @returns feed {Ref<Feed>} Current feed metadata
88
- * @returns timelines {Ref<Timeline[]>} Available timelines
89
- * @returns timelinesStates {Ref<Record<string, TimelineState>>} Per-timeline state records
90
- * @returns themeURL {Ref<string[]>} URLs for the active theme files
91
- * @returns theme {Ref<Theme>} Active light theme data
92
- * @returns darkTheme {Ref<Theme>} Active dark theme data
93
- * @returns timelineMode {Ref<string>} Current timeline display mode
94
- * @returns timelineType {Ref<string>} Current timeline type (e.g. 'Timeline' | 'SavedTimeline')
95
- * @returns sourceURL {Ref<string | null>} Source URL of the current feed
96
- * @returns baseUrl {Ref<string>} Base URL used to resolve relative paths
97
- * @returns metadataLoaded {Ref<boolean>} Whether feed metadata has been loaded
98
- * @returns initialBatchSize {Ref<number>} Number of stories in the first rendered batch
99
- * @returns filter {Ref<Record<string, unknown>>} Active filter state
100
- * @returns authorisationStatus {Ref<AuthorisationStatus>} Current user authorisation status
101
- * @returns issueAuthorisationStatus {Ref<IssueAuthorisationStatus>} Per-issue authorisation status
102
- * @returns openArticle {(story: Story, index: number) => Promise<unknown>} Opens a story in the native reader
103
- * @returns shareStory {(story: Story, index: number) => Promise<unknown>} Triggers the native share sheet
104
- * @returns setSaved {(story: Story, index: number, saved: boolean) => Promise<unknown>} Updates the saved state for a story
105
- * @returns setRead {(story: Story, index: number) => Promise<unknown>} Marks a story as read
106
- * @returns sendAnalytics {(type: string | Record<string, unknown>, story: Story, index?: number, dimensions?: AnalyticsDimensions) => Promise<unknown>} Sends an analytics event
107
- * @returns call {(action: string, payload?: unknown) => Promise<unknown>} Dispatches an arbitrary bridge action
108
- * @returns timelineIsReady {() => void} Signals that the Vue app has mounted
109
- * @returns fireToast {(type: string, action: string) => Promise<unknown>} Displays a native toast notification
110
- * @returns openAudio {(story: Story, fallback: Record<string, unknown>, index: number) => Promise<unknown>} Opens the native audio player
111
- * @returns openImageGallery {(images: StoryImage[] | undefined, initialImage?: number) => Promise<unknown>} Opens the native image gallery
112
- * @returns addToCalendar {(story: Story, index: number) => Promise<unknown>} Adds an event to the device calendar
113
- * @returns resolveIssueAccess {(story: Story) => boolean} Resolves whether a story is locked based on issue authorisation
114
- * @returns $on {(event: string, listener: (...args: unknown[]) => void) => () => void} Subscribes to a bridge event; returns an unsubscribe function
115
- * @returns $emit {(event: string, ...args: unknown[]) => void} Emits a bridge event
116
- * @returns $once {(event: string, listener: (...args: unknown[]) => void) => void} Subscribes to a bridge event for a single invocation
117
- * @returns bridge {BridgeService} The raw reactive bridge instance for direct access
118
- */
119
- function useBridge() {
120
- return {
121
- ...toRefs(reactiveBridge),
122
- ...boundActions,
123
- resolveIssueAccess: reactiveBridge.resolveIssueAccess,
124
- $on: (event, listener) => reactiveBridge.$on(event, listener),
125
- $emit: (event, ...args) => reactiveBridge.$emit(event, ...args),
126
- $once: (event, listener) => reactiveBridge.$once(event, listener),
127
- bridge: reactiveBridge
128
- };
129
- }
130
- //#endregion
131
- //#region src/vue/bootstrap.ts
132
- function useBootstrap(options = {}) {
133
- const { readyTimeout = 4e3 } = options;
134
- if ("scrollRestoration" in history) history.scrollRestoration = "manual";
135
- triggerPugpigUpdateSequence();
136
- injectThemeAndWait(readyTimeout);
137
- applyFeedClasses();
138
- }
139
- function injectThemeAndWait(readyTimeout = 4e3) {
140
- const loaded = [];
141
- const forceReady = setTimeout(() => {
142
- globalBridge.logInfo?.("debug", `${readyTimeout}ms have passed, forcing timelineIsReady`);
143
- globalBridge.timelineIsReady?.();
144
- }, readyTimeout);
145
- function onDepLoaded(name) {
146
- loaded.push(name);
147
- if (!["renderedRequiredStyles", ...globalBridge.themeFileNames ?? []].every((dep) => loaded.includes(dep))) return;
148
- clearTimeout(forceReady);
149
- document.fonts.ready.then(() => globalBridge.timelineIsReady?.());
150
- }
151
- for (const url of globalBridge.themeURL ?? []) {
152
- const link = document.createElement("link");
153
- link.rel = "stylesheet";
154
- link.href = url;
155
- link.onload = () => onDepLoaded(url);
156
- link.onerror = () => onDepLoaded(url);
157
- document.head.appendChild(link);
158
- }
159
- onDepLoaded("renderedRequiredStyles");
160
- }
161
- function applyFeedClasses(rootSelector = "#app") {
162
- const rootEl = document.querySelector(rootSelector);
163
- const classes = [...globalBridge.feed?.classes ?? []];
164
- if (globalBridge.isSavedTimeline) classes.push("saved-timeline");
165
- if (rootEl && classes.length) rootEl.classList.add(...classes);
166
- }
167
- function useDependencyTracker(options = {}) {
168
- const { readyTimeout = 4e3 } = options;
169
- const loadedDependencies = ref([]);
170
- const forceReady = setTimeout(() => {
171
- globalBridge.logInfo?.("debug", `${readyTimeout}ms have passed, forcing timelineIsReady`);
172
- globalBridge.timelineIsReady?.();
173
- }, readyTimeout);
174
- function onDependencyLoaded(dependencyName) {
175
- if (loadedDependencies.value.includes(dependencyName)) return;
176
- loadedDependencies.value.push(dependencyName);
177
- const isLoaded = [
178
- "mainView",
179
- "renderedRequiredStyles",
180
- ...globalBridge.themeFileNames ?? []
181
- ].every((dep) => loadedDependencies.value.includes(dep));
182
- globalBridge.logInfo?.("debug", `Theme dependency '${dependencyName}' loaded (${performance.now()}ms)`);
183
- if (!isLoaded) return;
184
- clearTimeout(forceReady);
185
- globalBridge.logInfo?.("debug", `Theme dependencies complete: ${loadedDependencies.value.join(", ")}`);
186
- document.fonts.ready.then(() => {
187
- globalBridge.logInfo?.("debug", `Document fonts loaded (${performance.now()}ms)`);
188
- globalBridge.timelineIsReady?.();
189
- });
190
- }
191
- onUnmounted(() => {
192
- clearTimeout(forceReady);
193
- });
194
- return {
195
- loadedDependencies,
196
- onDependencyLoaded
197
- };
198
- }
199
- //#endregion
200
- //#region src/vue/index.ts
201
- const globalBridge = window.boltBridge || reactive(rawBridge);
202
- window.boltBridge = globalBridge;
203
- const isDebug = window.location.search.includes("preview");
204
- const BoltBridgeVue = (app, callback) => {
205
- setupPugpigUpdate(globalBridge);
206
- app.config.globalProperties.$bridge = globalBridge;
207
- app.provide("globalBridge", globalBridge);
208
- app.use(installTooltipDirective);
209
- if (isDebug) import("./development-Bl8-5E3g.mjs").then(({ injectBridge }) => injectBridge(app, callback)).then(() => callback?.());
210
- else callback?.();
211
- };
212
- //#endregion
213
- export { BOOTSTRAP_METHODS, BoltBridgeVue, applyFeedClasses, globalBridge, injectThemeAndWait, isDebug, registerToolTips, triggerPugpigUpdateSequence, useBootstrap, useBridge, useDependencyTracker };
1
+ import{i as e,o as t,r as n,s as r,t as i}from"./chunks/bootstrap-B7Gwb_jl.mjs";import{onUnmounted as a,reactive as o,ref as s,toRefs as c,watchEffect as l}from"vue";const u=s([]),d=s([]),f=s(!1),p=(e,t)=>{if(d.value.includes(e))return;let n=new IntersectionObserver(([r])=>{if(r.isIntersecting){if(d.value.includes(e))return n.disconnect();u.value.push({id:e,el:t}),n.disconnect()}},{threshold:1});return n.observe(t),n};function m(){window.addEventListener(`scroll`,()=>{f.value=!0,window.clearTimeout(window.scrollTimeout),window.scrollTimeout=setTimeout(()=>f.value=!1,150)},!1),l(()=>{f.value||u.value.forEach((e,t)=>{if(d.value.includes(e.id))return;let{x:n,y:r,width:i,height:a}=e.el.getBoundingClientRect(),o={w:i,h:a,x:n,y:r+window.scrollY};window.parent?.pugpigBridgeService?.showToolTip?.(JSON.stringify({id:e.id,source:o})),u.value.splice(t,1),d.value.push(e.id)})})}function h({attribute:e=`tooltip`,observerOptions:t}={}){if(!window.parent?.pugpigBridgeService?.showToolTip)return;m();let n=t=>{t.forEach(t=>t.addedNodes.forEach(t=>{if(t.nodeType!==Node.ELEMENT_NODE)return;let n=t,r=n.getAttribute(e);r&&(d.value.includes(r)||p(r,n))}))},r=new MutationObserver(n);n([{addedNodes:document.body.childNodes}]),r.observe(document.body,t??{childList:!0,subtree:!0})}function g(e){if(!window.parent?.pugpigBridgeService?.showToolTip){e.directive(`tooltip`,{});return}m(),e.directive(`tooltip`,{mounted:(e,t)=>p(t.value,e)})}const _=window.boltBridge||o(t);function v(e,t){return Object.fromEntries(Object.entries(e).map(([e,n])=>[e,typeof n==`function`?n.bind(t):n]))}const y=v(r,_);function b(){return{...c(_),...y,resolveIssueAccess:_.resolveIssueAccess,$on:(e,t)=>_.$on(e,t),$emit:(e,...t)=>_.$emit(e,...t),$once:(e,t)=>_.$once(e,t),bridge:_}}function x(e={}){let{readyTimeout:t=4e3}=e;`scrollRestoration`in history&&(history.scrollRestoration=`manual`),n(),S(t),C()}function S(e=4e3){let t=[],n=setTimeout(()=>{T.logInfo?.(`debug`,`${e}ms have passed, forcing timelineIsReady`),T.timelineIsReady?.()},e);function r(e){t.push(e),[`renderedRequiredStyles`,...T.themeFileNames??[]].every(e=>t.includes(e))&&(clearTimeout(n),document.fonts.ready.then(()=>T.timelineIsReady?.()))}for(let e of T.themeURL??[]){let t=document.createElement(`link`);t.rel=`stylesheet`,t.href=e,t.onload=()=>r(e),t.onerror=()=>r(e),document.head.appendChild(t)}r(`renderedRequiredStyles`)}function C(e=`#app`){let t=document.querySelector(e),n=[...T.feed?.classes??[]];T.isSavedTimeline&&n.push(`saved-timeline`),t&&n.length&&t.classList.add(...n)}function w(e={}){let{readyTimeout:t=4e3}=e,n=s([]),r=setTimeout(()=>{T.logInfo?.(`debug`,`${t}ms have passed, forcing timelineIsReady`),T.timelineIsReady?.()},t);function i(e){if(n.value.includes(e))return;n.value.push(e);let t=[`mainView`,`renderedRequiredStyles`,...T.themeFileNames??[]].every(e=>n.value.includes(e));T.logInfo?.(`debug`,`Theme dependency '${e}' loaded (${performance.now()}ms)`),t&&(clearTimeout(r),T.logInfo?.(`debug`,`Theme dependencies complete: ${n.value.join(`, `)}`),document.fonts.ready.then(()=>{T.logInfo?.(`debug`,`Document fonts loaded (${performance.now()}ms)`),T.timelineIsReady?.()}))}return a(()=>{clearTimeout(r)}),{loadedDependencies:n,onDependencyLoaded:i}}const T=window.boltBridge||o(t);window.boltBridge=T;const E=window.location.search.includes(`preview`),D=(t,n)=>{e(T),t.config.globalProperties.$bridge=T,t.provide(`globalBridge`,T),t.use(g),E?import(`./chunks/development-CzUmtUQR.mjs`).then(({injectBridge:e})=>e(t,n)).then(()=>n?.()):n?.()};export{i as BOOTSTRAP_METHODS,D as BoltBridgeVue,C as applyFeedClasses,T as globalBridge,S as injectThemeAndWait,E as isDebug,h as registerToolTips,n as triggerPugpigUpdateSequence,x as useBootstrap,b as useBridge,w as useDependencyTracker};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pugpigbolt/bridge",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "dist"