//#region src/types.d.ts /** * Anything a plugin accepts as its target: a CSS selector, a single element, * a list of elements, or a jQuery collection (if jQuery is on the page). */ type ElementInput = string | Element | Element[] | NodeListOf | ArrayLike; /** * Common shape returned by every plugin instance so callers always have a * predictable way to tear a plugin down. */ interface PluginInstance { /** Removes listeners/observers and undoes DOM changes made by the plugin. */ destroy(): void; } //#endregion //#region src/plugins/adsenseLoader.d.ts /** Configuration for {@link adsenseLoader}. */ interface AdsenseLoaderOptions { /** * Start loading this many pixels before the wrapper enters the * viewport. Default `"200px"`. */ rootMargin?: string; /** `IntersectionObserver` threshold. Default `0`. */ threshold?: number | number[]; /** Watch for wrapper elements inserted after init (e.g. infinite-scroll posts). Default `true`. */ observeMutations?: boolean; /** Root element to scan/observe within. Default `document.body`. */ container?: Element | Document; /** * Media query that decides which of `data-mobile-size` / * `data-pc-size` a wrapper resolves to. Default * `"(max-width: 767px)"`. */ mobileBreakpoint?: string; /** * If the ad comes back unfilled or fails to load, remove the wrapper * from the DOM entirely (matching the old plugin's behavior) rather * than leaving a dead, empty slot. Default `true`. */ removeOnUnfilled?: boolean; /** Called right before a wrapper's ad starts loading. */ onLoad?: (wrapper: HTMLElement) => void; /** Called once a wrapper's ad has actually filled. */ onFilled?: (wrapper: HTMLElement) => void; /** * Called when a wrapper's ad comes back unfilled or fails to load — * right before it's removed (if `removeOnUnfilled` is on). Use this * for a fallback instead of relying on the (about to be gone) wrapper. */ onUnfilled?: (wrapper: HTMLElement) => void; } /** Returned by {@link adsenseLoader}. */ type AdsenseLoaderInstance = PluginInstance; /** * Lazy-loads AdSense units wrapped in a container div — `
` — right as * each one is about to enter the viewport, using `IntersectionObserver` * instead of scroll/resize polling. * * Also supports responsive sizing: give a wrapper `data-mobile-size` * and/or `data-pc-size` listing candidate sizes as `heightxwidth` pairs * (height first), and the plugin picks the best-fitting one for the * current breakpoint/width and applies it to the wrapper directly — * before the ad loads, so it never resizes an already-filled ad (see the * policy note below). * * ```html *
* *
* ``` * * > **On ad refresh:** AdSense's publisher policy does not permit * > programmatically refreshing an already-served ad. This plugin * > resizes a wrapper's own CSS box before its ad loads — it never * > touches, resizes, or reloads an ad that has already filled. * * @param input - Selector, element(s), or jQuery collection for the * `.adsense`-style wrapper(s) to lazy-load. * @param options - {@link AdsenseLoaderOptions} * @returns An {@link AdsenseLoaderInstance} — `destroy()` disconnects * every observer and restores any wrapper that never filled to its * original markup (filled ads are left exactly as AdSense rendered them). * * @example * ```ts * import { adsenseLoader } from "blogr-plugins"; * * adsenseLoader(".adsense", { * rootMargin: "200px", * onFilled: (wrapper) => wrapper.classList.add("adsense--loaded"), * onUnfilled: (wrapper) => console.log("no fill for", wrapper), * }); * ``` */ declare function adsenseLoader(input: ElementInput, options?: AdsenseLoaderOptions): AdsenseLoaderInstance; //#endregion //#region src/plugins/avatarify.d.ts /** * Any [DiceBear](https://www.dicebear.com/styles) style name (`"thumbs"`, * `"bottts"`, `"initials"`, `"identicon"`, ...). Kept as a plain `string` * rather than a strict union so new DiceBear styles work without a type * update. */ type AvatarStyle = "adventurer" | "adventurer-neutral" | "avataaars" | "avataaars-neutral" | "big-ears" | "big-ears-neutral" | "big-smile" | "blobs" | "bottts" | "bottts-neutral" | "clay" | "constellation" | "critters" | "croodles" | "croodles-neutral" | "disco" | "dylan" | "fun-emoji" | "glass" | "glyphs" | "icons" | "identicon" | "initial-face" | "initials" | "landscape" | "loops" | "lorelei" | "lorelei-neutral" | "micah" | "miniavs" | "moods" | "notionists" | "notionists-neutral" | "open-peeps" | "personas" | "pixel-art" | "pixel-art-neutral" | "pixelbot" | "planets" | "rings" | "shape-grid" | "shapes" | "sprouts" | "squircles" | "stripes" | "thumbs" | "toon-head" | "triangles" | "waves" | "weave"; /** Detail passed to `onAvatarSet`. */ interface AvatarSetDetail { /** The username the avatar was generated for. */ username: string; /** The generated avatar URL that was applied. */ url: string; /** The element matched by `usernameSelector`. */ usernameEl: Element; /** The element matched by `avatarSelector` that received the avatar. */ avatarEl: Element; } /** Detail passed to `onSuccess`. */ interface AvatarSuccessDetail extends AvatarSetDetail { /** Increments once per avatar that actually finishes loading, in load order — use to log/track individual images. */ index: number; /** Stable id for this avatar: `avatarEl.id` if the element has one, else `"avatar-{index}"`. */ id: string; } /** Configuration for {@link avatarify}. */ interface AvatarifyConfig { /** * Root element to watch (selector, element(s), or jQuery collection). * The `MutationObserver` (detect dynamically-added comments) watches * this element. Optional — if omitted, falls back to the closest * ancestor of the first element matching `commentSelector`, then of the * first element matching `avatarSelector`, then to `document.body`. */ container?: ElementInput; /** Selector (relative to a comment element) for the commenter's username. **Required.** */ usernameSelector: string; /** Selector for the comment element that wraps one username + timestamp + avatar. **Required.** */ commentSelector: string; /** Selector (relative to a comment element) for the profile-picture element. **Required.** */ avatarSelector: string; /** * Selector (relative to a comment element) for the timestamp element. * Omit to leave the timestamp out of the avatar seed entirely (every * comment from the same username then gets the same avatar). */ timestampSelector?: string; /** * Attribute on the timestamp element to read (e.g. `"data-datetime"`). * Omitted/falsy reads the element's text content instead. */ timestampAttribute?: string; /** * `true` replaces every avatar, even ones that already have a real * image. `false` (default) leaves real avatars' image alone but still * re-applies them onto `avatarAttribute`'s target (src/background-image) * if that differs from where the image currently lives. */ setRandomAvatarForAll?: boolean; /** * Forces how avatar gets applied: `"src"` sets `src` attr, * `"background-image"` sets inline `background-image` style. Omit for * auto-detect: elements with `avatarDataAttribute` set or non-`` * tags get `background-image`, plain `` tags get `src`. */ avatarAttribute?: "src" | "background-image"; /** * Data-attribute NAME that holds a real avatar url directly (Blogger * lazy-src style, e.g. `data-image="//..."`). Checked before `src`/css * bg when reading the current image. Default `"data-avatar"` — set * this to match your markup, e.g. `"data-image"`. */ avatarDataAttribute?: string; /** DiceBear style to request. Default `"thumbs"`. */ avatarStyle?: AvatarStyle; /** * Background-image/`src` substrings that count as "no avatar set" — * checked with `.includes()`. Extend this if your theme's blank * placeholder isn't one of the two Blogger defaults already covered. * An avatar with no image at all (`background-image: none` / no `src`) * always counts as empty regardless of this list. */ emptyAvatarPatterns?: (string | RegExp)[]; /** DiceBear API version segment. Default `"7.x"`. */ dicebearVersion?: string; /** * Full URL template overriding DiceBear entirely — `{style}` and * `{seed}` are replaced (seed is pre-encoded). Use this to point at a * self-hosted avatar service instead. */ apiUrl?: string; /** * Overrides how the per-comment seed string is built. Default: the * username alone when `avatarStyle` is `"initials"`, otherwise the * username with the timestamp appended (so re-commenting the same text * still gets a distinct avatar per comment). */ seed?: (username: string, timestamp: string) => string; /** * `rootMargin` for each avatar's own lazy-load `IntersectionObserver` — * every avatar loads independently once it nears the viewport, so only * on-screen (or about-to-be) avatars ever fetch. Default `"0px"`. */ rootMargin?: string; /** Debounce (ms) applied to `MutationObserver`-triggered rescans, so a batch of DOM changes only triggers one pass. Default `150`. */ debounce?: number; /** Called once per avatar actually set (fires right after the url is assigned to the DOM). */ onAvatarSet?: (detail: AvatarSetDetail) => void; /** * Called once per avatar, separately, after its image actually finishes * loading (real success — not just DOM assignment). Gets `index`/`id` * so you can tell which avatar loaded. */ onSuccess?: (detail: AvatarSuccessDetail) => void; /** Called on a recoverable issue (selector matched nothing, etc). Defaults to `console.error`. */ onError?: (message: string) => void; } /** Returned by {@link avatarify}. */ interface AvatarifyInstance extends PluginInstance { /** Forces an immediate load of every matched avatar, bypassing the debounce and the per-avatar in-view gate. */ refresh(): void; } /** * Auto-generates a [DiceBear](https://www.dicebear.com) avatar for every * commenter who doesn't already have one — built for Blogger's native * comment widget, where anonymous/no-photo commenters get a blank * placeholder image. Each avatar lazy-loads independently (only fetched * once it nears the viewport) and a `MutationObserver` keeps watching so * comments added later — pagination, "load more", async widgets — get * avatars too. * * @param config - {@link AvatarifyConfig} * @returns An {@link AvatarifyInstance} — `destroy()` stops both observers * (already-set avatars are left in place); `refresh()` force-loads every * matched avatar immediately. * * @example * ```ts * import { avatarify } from "blogr-plugins"; * * avatarify({ * container: "#comments", * usernameSelector: ".cmHr .n bdi", * commentSelector: ".c", * timestampSelector: ".d.dtTm", * timestampAttribute: "data-datetime", * avatarSelector: ".cmAv .im", * setRandomAvatarForAll: true, * avatarStyle: "thumbs", * }); * ``` */ declare function avatarify(config: AvatarifyConfig): AvatarifyInstance; //#endregion //#region src/plugins/cookify.d.ts /** Options accepted when writing a cookie with {@link cookify}. */ interface CookifySetOptions { /** Days until expiry. Omit for a session cookie. */ expiresDays?: number; /** Cookie path. Default `"/"`. */ path?: string; /** Cookie domain. */ domain?: string; /** Send only over HTTPS. */ secure?: boolean; /** SameSite policy. Default `"Lax"`. */ sameSite?: "Strict" | "Lax" | "None"; } interface Cookify { /** * Writes a cookie. * @param name - Cookie name. * @param value - Any JSON-serializable value. * @param options Configuration object. * See {@link CookifySetOptions}. */ set(name: string, value: unknown, options?: CookifySetOptions): void; /** * Reads a cookie. * @param name - Cookie name. * @returns Parsed value, or `undefined` if not set. */ get(name: string): T | undefined; /** * Reads every cookie. * @returns Record containing all cookies. */ getAll(): Record; /** * Deletes a cookie. * @param name - Cookie name. * @param options - Must match `path`/`domain` used when setting cookie. * @returns `true` if cookie existed. */ remove(name: string, options?: Pick): boolean; } /** * Small, dependency-free cookie utility (a typed replacement for the classic * `js-cookie` plugin). Values are JSON-encoded automatically, so you can * store strings, numbers, booleans, or plain objects/arrays. * * @example * ```ts * import { cookify } from "blogr-plugins"; * cookify.set("theme", "dark", { expiresDays: 365 }); * cookify.get("theme"); // "dark" * cookify.remove("theme"); * ``` */ declare const cookify: Cookify; //#endregion //#region node_modules/blogr/dist/blogr.d.ts //#region src/types/feed.d.ts /** An author of a post, page, comment or the blog itself. */ interface Author { /** Display name of the author, or `null` if unavailable. */ name: string | null; /** Profile URL of the author, or `null` if unavailable. */ url: string | null; /** Avatar/profile image URL of the author, or `null` if unavailable. */ image: string | null; } /** A single `` entry as reported by the feed. */ interface Link { rel: string; href: string; type: string | null; title: string | null; } /** Geo-location info attached to a post, if any. */ interface Geo { box: string | null; featureName: string | null; point: string | null; } /** Extra info attached to a comment entry. */ interface Extended { /** CSS class assigned to the commenter, if any. */ class: string | null; /** Human formatted publish time, if any. */ time: string | null; /** Whether the comment has been removed/moderated. */ removed: boolean; } /** Metadata about comments attached to a post. */ interface PostCommentInfo { feed: string | null; number: number | null; title: string | null; } /** A Blogger post or page entry. */ interface Post { /** Entry id (numeric string). */ id: string; /** Title of the entry. */ title: string; /** Canonical URL of the entry. */ url: string; /** ISO published timestamp. */ published: string; /** ISO last-updated timestamp. */ updated: string; /** Labels attached to the entry. */ labels: string[]; /** Entry author. */ author: Author; /** Full HTML content, or `null` when only a summary was requested. */ content: string | null; /** Plain-text/HTML summary/snippet, or `null`. */ summary: string | null; /** Best-guess thumbnail extracted from content, or `null`. */ thumbnail: string | null; /** Thumbnail explicitly selected by Blogger, or `null`. */ thumbnailAlt: string | null; /** Comment count/metadata for this entry. */ comments: PostCommentInfo; /** Geo-location, if attached. */ geo: Geo; /** Raw `` entries from the feed. */ links: Link[]; } /** A comment entry. */ interface Comment { id: string; title: string; url: string; published: string; updated: string; author: Author; content: string | null; summary: string | null; extended: Extended; /** The post this comment belongs to. */ post: { id: string; url: string; }; /** Id of the parent comment when this is a reply, else `null`. */ inReplyTo: string | null; links: Link[]; } //#endregion //#region src/plugins/resizeImage.d.ts /** Recognized YouTube thumbnail quality presets. */ type YouTubeThumbnailQuality = "default" | "mqdefault" | "hqdefault" | "sddefault" | "maxresdefault"; /** Configuration options for {@link resizeImage}. */ interface ResizeImageOptions { /** Output height in px. Default `360`. */ height?: number; /** Output width in px. Default `640`. */ width?: number; /** Crop shape. Default: leave any existing crop untouched. */ crop?: "circle" | "square"; /** Output image format. Default `"webp"`. */ format?: "jpeg" | "png" | "webp"; /** Flip direction. Default: leave any existing flip untouched. */ flip?: "horizontally" | "vertically"; /** Rotation in degrees — `90`, `180`, or `270`. Default: leave any existing rotation untouched. */ rotate?: number; /** * Quality preset for YouTube thumbnail URLs. Ignored for Blogger images. * Default `"maxresdefault"`. YouTube thumbnails are always served as * WebP, so `format`/`width`/`height`/`crop`/`flip`/`rotate` don't apply. */ ytThumbnail?: YouTubeThumbnailQuality; } /** * Checks whether a URL is a Blogger/Google-hosted image (old or new URL * shape) or a YouTube video thumbnail that {@link resizeImage} can handle. * * @param url - Image URL to check. * @returns `true` if the URL is a recognized Blogger image or YouTube thumbnail. * * @example * ```ts * import { isSupportedImage } from "blogr-plugins"; * isSupportedImage("https://1.bp.blogspot.com/path/s72-c/image.jpg"); // true * isSupportedImage("https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg"); // true * ``` */ declare function isSupportedImage(url: string | URL): boolean; /** * Builds a resized/transformed URL for a Blogger/Google-hosted image. * Unsupported URLs are returned unchanged rather than throwing, so it's * always safe to run any image URL through this function. * * For Blogger images, this parses the URL's existing param segment and * only overrides the params implied by `options` — width, height and * format always apply (falling back to their defaults), while crop, flip * and rotate are left untouched unless explicitly requested. Any other * recognized param already on the URL (e.g. `nu`, `pd`, `d`) is preserved. * * For YouTube thumbnail URLs, `width`/`height`/`crop`/`format`/`flip`/`rotate` * are ignored — YouTube only serves fixed quality presets — and only * `ytThumbnail` applies, always rewritten to the WebP variant. * * @param url - Source image or YouTube thumbnail URL. * @param options Configuration object. * See {@link ResizeImageOptions}. * @returns The transformed image URL, or the original URL if unsupported. * * @example * ```ts * import { resizeImage } from "blogr-plugins"; * * const url = resizeImage("https://1.bp.blogspot.com/path/s72-c/image.jpg", { * width: 400, * height: 400, * format: "webp", * }); * ``` */ declare function resizeImage(url: string | URL, options?: ResizeImageOptions): string; /** * Applies {@link resizeImage} to every matched element in place — `` * (`src` + `srcset`) or any element with an inline `background-image`. * Elements matching neither are left untouched. No setup call required. * * @param input - Selector, element(s), or jQuery collection to resize. * @param options Configuration object. * See {@link ResizeImageOptions}. * * @example * ```ts * import { resizeImageInDom } from "blogr-plugins"; * resizeImageInDom(".post-thumb img", { width: 400, height: 400 }); * resizeImageInDom(".thumb", { ytThumbnail: "mqdefault" }); * ``` */ declare function resizeImageInDom(input: ElementInput, options?: ResizeImageOptions): void; //#endregion //#region src/plugins/createWidget.d.ts /** What data the widget lists — one flag covers both feed and shape. */ type WidgetType = "posts" | "pages" | "comments" | "authors" | "labels"; /** How the initial batch of entries is sourced. */ type WidgetSourceType = "recent" | "random"; /** Feed field a widget's entries are ordered by. */ type WidgetOrderBy = "published" | "updated"; /** Direction entries are shown in, applied after fetching. */ type WidgetSort = "asc" | "desc"; /** * A normalized post or page — every field from the raw feed entry (id, url, * author, labels, comments, geo, links, etc.) is spread directly onto this * object. `summary`/`published`/`updated`/`thumbnail` are overridden with * processed values; everything else is exactly what the feed returned. */ interface PostEntry extends Omit { kind: "posts" | "pages"; /** Publish date, formatted per `dateFormat`. */ published: string; /** Last-updated date, formatted per `dateFormat`. */ updated: string; /** Plain text — HTML tags and comments stripped — truncated to `summaryLength` characters. */ summary: string; /** Resized thumbnail (via {@link resizeImage}), falling back to `fallbackImage`. `""` when `thumbnail: false`. */ thumbnail: string; } /** * A normalized comment — every field from the raw comment feed entry (id, * url, author, post, inReplyTo, extended, etc.) is spread directly onto * this object rather than nested under `raw`. `summary`/`published`/ * `updated` are overridden with truncated/formatted values; everything * else is exactly what the feed returned. */ interface CommentEntry extends Omit { kind: "comments"; summary: string; published: string; updated: string; } /** * A normalized author — every field from `blogr`'s `Author` is spread * directly onto this object. `id`/`name`/`url`/`image` are overridden with * fallback-filled values; `email`/`imageWidth`/`imageHeight` pass through * unchanged. */ interface AuthorEntry extends Omit { kind: "authors"; id: string; name: string; url: string; image: string; } /** * A normalized label — Blogger's `labels()` returns bare strings, so * there's no raw object to spread; this is just that string (humanized, * e.g. `"live-wallpaper"` -> `"Live Wallpaper"`) plus a built search link. */ interface LabelEntry { kind: "labels"; id: string; name: string; url: string; } type WidgetEntry = PostEntry | CommentEntry | AuthorEntry | LabelEntry; /** * Transforms one normalized entry, e.g. to inject a computed field, rewrite * a value from a transformer chain, or pull in data from elsewhere. Applied * in array order — each transformer receives the previous one's output. * May be async. Return `null` to drop the entry from the batch entirely. */ type WidgetTransformer = (entry: WidgetEntry, index: number) => WidgetEntry | null | Promise; /** Configuration for {@link createWidget}. */ interface CreateWidgetOptions { /** Enable JSONP transport (browser-only). @default true */ jsonp?: boolean; /** * What the widget lists. * - "posts": Blog posts (default) * - "pages": Static pages * - "comments": Comments * - "authors": Distinct post authors * - "labels": Labels/categories * `"pages"`/`"comments"`/`"authors"`/`"labels"` ignore `labels`/`query`/ * `related` (Blogger's feed API doesn't support filtering those feeds * that way, and authors/labels aren't filterable at all). * @default "posts" */ type?: WidgetType; /** * How the initial batch is sourced: `"recent"` lists newest-first, * `"random"` samples random entries. Only applies to `type: "posts"`. * Default `"recent"`. */ source?: WidgetSourceType; /** Where the widget mounts and renders. **Required.** */ containerSelector: ElementInput; /** URL (or numeric id) of the Blogger blog to read from. **Required.** */ blogUrl: string; /** Labels to filter by (AND semantics — an entry must carry every one). Empty/omitted = no label filter. Only applies to `type: "posts"`. */ labels?: string[]; /** Feed field to sort by. Default `"published"`. */ orderBy?: WidgetOrderBy; /** Direction to show entries in. Default `"desc"`. */ sort?: WidgetSort; /** Search query. Combine with `deepSearch` to control how it's applied. */ query?: string; /** * `true`: every `setQuery()`/query change re-fetches from the network. * `false`: fetches a broader buffer once, then filters/searches inside * it client-side without any further network requests. Default `false`. */ deepSearch?: boolean; /** * Token-based date format applied to `published`/`updated`. Supports * `yyyy yy MMMM MMM MM M dd d EEEE EEE HH hh mm ss a`. Default * `"MMM d, yyyy"`. */ dateFormat?: string; /** * Only include entries that share at least one label with the post * identified by `currentPostId`. Requires `currentPostId`. Default `false`. */ related?: boolean; /** Shuffle the final rendered order (independent of `source`). Default `false`. */ random?: boolean; /** Drop `currentPostId` from the results. Default `false`. */ excludeCurrent?: boolean; /** * Id of the post the widget is shown alongside — required for `related` * and `excludeCurrent` to do anything. Not part of the original spec's * prop list, but both of those options are meaningless without it, so * it's added here; falls back to ``'s id-bearing * query param when omitted, or does nothing if that can't be found. */ currentPostId?: string; /** * `"default"` resizes each entry's own/extracted thumbnail with * {@link resizeImage}'s defaults. Pass a {@link ResizeImageOptions} * object to customize width/height/crop/etc. `false` disables * thumbnails entirely (skips extraction and rendering). Default `"default"`. */ thumbnail?: false | "default" | ResizeImageOptions; /** Shown when an entry has no image of its own. Defaults to a small built-in placeholder. */ fallbackImage?: string; /** Max characters of plain-text summary kept in `entry.summary`. `0` disables truncation. Default `120`. */ summaryLength?: number; /** Auto-load more entries via `IntersectionObserver` as the user scrolls near the end. Default `false`. */ infiniteScroll?: boolean; /** Render a "load more" button. Can be combined with `infiniteScroll`. Default `false`. */ loadMore?: boolean; /** Label for the load-more button. Default `"Load more"`. */ loadMoreText?: string; /** Entries fetched/shown per batch. Default `6`. */ maxVisibleItems?: number; /** * `rootMargin` for the `IntersectionObserver`s used both to defer the * widget's first fetch until its container nears the viewport, and to * trigger `infiniteScroll`. Default `"0px"`. */ rootMargin?: string; /** * Persist fetched entries in `localStorage` (keyed by `cacheKey`) so a * fresh page load can skip the network entirely within `cacheTTL`. * Separate from and in addition to `blog.cache` (the SDK's own * in-memory, per-session response cache), which this also enables. * Default `false`. */ cache?: boolean; /** Cache key. Defaults to `containerSelector` (as a string) or `"widget"`. */ cacheKey?: string; /** How long a cached batch stays valid, in seconds. Default `3600` (1 hour). */ cacheTTL?: number; /** Applied to every entry, in order, right after normalization. */ transformers?: WidgetTransformer[]; /** Called right before each network fetch. May be async. */ beforeFetch?: () => void | Promise; /** Called with the normalized batch right after a successful fetch, before rendering. May be async. */ afterFetch?: (entries: WidgetEntry[]) => void | Promise; /** Called for each entry right before it's rendered. */ beforeRender?: (entry: WidgetEntry) => void; /** Called after an entry's element has been inserted into the DOM. */ afterRender?: (element: HTMLElement, entry: WidgetEntry) => void; /** Called when a fetch or render step throws. */ onError?: (err: unknown) => void; /** Called whenever there are zero entries to show (initial load or after filtering). */ onEmpty?: () => void; /** Renders the loading state. `status` is a short human-readable phase, e.g. `"Loading posts..."`. */ loading?: (status: string) => string; /** Renders the error state. */ error?: (errorMsg: string) => string; /** Renders the empty state. */ empty?: () => string; /** Renders one entry. `i` is its index in the currently rendered batch. */ template?: (entry: WidgetEntry, i: number) => string; /** Extra class name(s) for an entry's wrapper element. */ entryClass?: (entry: WidgetEntry, index: number) => string; } /** Returned by {@link createWidget}. */ interface WidgetInstance extends PluginInstance { /** Re-fetches from scratch, bypassing the local cache. */ refresh(): Promise; /** Updates the search query and re-fetches (or re-filters, per `deepSearch`). */ setQuery(query: string): Promise; } /** * Builds and mounts a fully self-contained Blogger listing widget — related * posts, a recent-posts sidebar, random picks, a comment stream, or a page * list — backed by the [`blogr`](https://jsr.io/@oyzamil/blogr) SDK. Fetches * are deferred until the container scrolls near the viewport, thumbnails are * resized via {@link resizeImage}, and results can be paged with an * infinite-scroll sentinel and/or a "load more" button. * * @param options Configuration object. * See {@link CreateWidgetOptions}. * @returns A {@link WidgetInstance} — `destroy()` tears down every observer * and clears the container; `refresh()`/`setQuery()` let you drive it after * the fact. * * @example * ```ts * import { createWidget } from "blogr-plugins"; * * const widget = createWidget({ * containerSelector: "#relatedPosts", * blogUrl: "https://example.blogspot.com", * type: "posts", // or "pages" | "comments" | "authors" | "labels" * related: true, * excludeCurrent: true, * currentPostId: "1234567890123456789", * labels: ["javascript"], * maxVisibleItems: 6, * loadMore: true, * template: (entry) => ` *
* ${entry.title} *

${entry.title}

*

${entry.summary}

*
* `, * }); * * // later, e.g. before a client-side route change * widget.destroy(); * ``` */ declare function createWidget(options: CreateWidgetOptions): WidgetInstance; //#endregion //#region src/plugins/lazify.d.ts /** Configuration options for {@link lazify}. */ interface LazifyOptions { /** Attribute holding the real media URL. Default `"data-src"`. */ attribute?: string; /** Attribute holding a `