PageSourceSearch

https://www.beigewum.at/wp-content/plugins/bluesky-feed-for-wordpr…/assets/js/feed-renderer.js?ver=1.2.0

js beigewum.at collected 2026-09-24 18:57:40 UTC 9,752 bytes, 190 lines download raw bytes

1function renderFeed(container, settings) {
2    const { username, postCount, includePins, includeLink, theme } = settings;
3
4    if (!username) {
5        container.innerHTML = "<p>Please enter a valid username above to see the feed.</p>";
6        return;
7    }
8
9    // Apply the selected theme
10    container.classList.remove("theme-light", "theme-dim", "theme-dark");
11    container.classList.add(`theme-${theme}`);
12
13    container.innerHTML = "<p>Loading feed...</p>";
14
15    axios
16        .get("https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed", {
17            params: {
18                actor: username,
19                limit: postCount,
20                filter: "posts_no_replies",
21                includePins: includePins === 1,
22            },
23        })
24        .then((response) => {
25            const { feed: posts } = response.data;
26
27            container.innerHTML = "";
28
29            if (!posts || posts.length === 0) {
30                container.innerHTML = "<p>No posts found for this author.</p>";
31                return;
32            }
33
34            posts.forEach((item) => {
35                const post = item.post;
36
37                // Skip pinned posts if includePins is false.
38                if (!includePins && post.record?.pinned) {
39                    return;
40                }
41
42                const authorName = post.author.displayName || post.author.handle;
43                const authorAvatar = post.author.avatar || "https://bsky.social/default-avatar.png";
44                const content = post.record?.text || "[No text content]";
45                const createdAt = new Date(post.indexedAt).toLocaleString();
46
47                const postUriSegments = post.uri.split("/");
48                const postId = postUriSegments.pop();
49                const handle = post.author.handle;
50                const postUrl = `https://bsky.app/profile/${handle}/post/${postId}`;
51
52                const replyCount = post.replyCount || 0;
53                const repostCount = post.repostCount || 0;
54                const likeCount = post.likeCount || 0;
55
56                // Check if the post is a repost (retweet).
57                let repostIndicator = "";
58                if (item.reason && item.reason.$type === "app.bsky.feed.defs#reasonRepost") {
59                    const originalAuthor = item.reason.by.displayName || item.reason.by.handle;
60                    repostIndicator = `
61                        <div class="bluesky-repost-indicator">
62                            <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-repeat" width="18" height="18" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
63                                <path stroke="none" d="M0 0h24V24H0z" fill="none"></path>
64                                <path d="M8 9l-4 -4l4 -4"></path>
65                                <path d="M16 15l4 4l-4 4"></path>
66                                <path d="M4 5h11a4 4 0 0 1 4 4v4"></path>
67                                <path d="M20 19h-11a4 4 0 0 1 -4 -4v-4"></path>
68                            </svg>
69                            <span>Reposted from <strong>${originalAuthor}</strong></span>
70                        </div>
71                    `;
72                }
73
74                // Extract media images (Force animated GIFs to load correctly)
75                let mediaHtml = "";
76                if (post.embed?.images) {
77                    mediaHtml = `<div class="bluesky-images">`;
78                    post.embed.images.forEach((image) => {
79                        const isGif = image.fullsize.toLowerCase().includes(".gif");
80                        let fullsizeUrl = image.fullsize;
81                        if (isGif) {
82                            fullsizeUrl = fullsizeUrl.split("?")[0];
83                        }
84                        mediaHtml += `
85                            <img src="${isGif ? fullsizeUrl : image.thumb}" 
86                                alt="Bluesky Post Image" 
87                                class="bluesky-image">
88                        `;
89                    });
90                    mediaHtml += `</div>`;
91                }
92
93                // Extract external link previews (cards) with large image
94                let linkPreviewHtml = "";
95                if (post.embed?.external) {
96                    const { uri, title, description, thumb } = post.embed.external;
97                    const isGif = uri.toLowerCase().includes(".gif");
98                    let gifUrl = uri; // Use original uri by default
99                    if (isGif) {
100                         gifUrl = uri.split("?")[0]; // Remove query parameters if it's a GIF
101                    }
102
103                    linkPreviewHtml = `
104                        <div class="bluesky-link-preview">
105                            <a href="${uri}" target="_blank" rel="noopener noreferrer" class="bluesky-link-card">
106                                ${
107                                    isGif && thumb // Check if thumb exists for GIF, otherwise it might be a direct GIF link
108                                        ? `<img src="${gifUrl}" alt="Animated GIF" class="bluesky-gif-image">` // If it's a GIF and we want to display it directly
109                                        : (thumb ? `<img src="${thumb}" alt="Link preview image" class="bluesky-link-image-large">` : '') + // Standard image preview
110                                          `<div class="bluesky-link-info">
111                                                <strong>${title}</strong>
112                                                <p>${description}</p>
113                                           </div>`
114                                }
115                            </a>
116                        </div>
117                    `;
118                }
119
120                // Format post content to preserve line breaks, clickable full URLs, and hashtags
121                const formattedContent = content
122                    .replace(/\n/g, "<br>")
123                    .replace(/((https?:\/\/[^\s]+))/g, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>')
124                    .replace(/(^|\s)(#[a-zA-Z0-9_]+)/g, '$1<span class="bluesky-hashtag">$2</span>');
125
126                const postElement = document.createElement("div");
127                postElement.className = "bg-white dark:bg-gray-900 dim:bg-gray-800 p-4 rounded-md shadow-md mb-4";
128
129                // Define the main body of the post (header, text, media, date)
130                const postBodyHtml = `
131                    <div class="bluesky-post-header">
132                        <img src="${authorAvatar}" alt="${authorName}" class="bluesky-avatar">
133                        <span class="bluesky-author">${authorName}</span>
134                    </div>
135                    <div class="text-gray-700 dark:text-gray-300 dim:text-gray-400 mt-2">${formattedContent}</div>
136                    ${mediaHtml}
137                    ${linkPreviewHtml}
138                    <div class="text-gray-500 text-sm mt-2">${createdAt}</div>
139                `;
140
141                // Conditionally wrap the post body with a link or a div
142                let wrappedPostBody;
143                if (includeLink && postUrl) {
144                    wrappedPostBody = `<a href="${postUrl}" target="_blank" rel="noopener noreferrer" class="bluesky-post-link">${postBodyHtml}</a>`;
145                } else {
146                    // If not linking, wrap in a div, possibly with the same class if it carries styling
147                    wrappedPostBody = `<div class="bluesky-post-link-wrapper">${postBodyHtml}</div>`;
148                }
149
150                // Define the reaction icons HTML
151                const reactionsHtml = `
152                    <div class="reaction-icons mt-4">
153                        <div class="reaction-item">
154                            <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-message-circle" width="20" height="20" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
155                                <path stroke="none" d="M0 0h24V24H0z" fill="none"></path>
156                                <path d="M3 21v-4a9 9 0 1 1 4 4h-4"></path>
157                            </svg>
158                            <span>${replyCount}</span>
159                        </div>
160                        <div class="reaction-item">
161                            <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-heart" width="20" height="20" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
162                                <path stroke="none" d="M0 0h24V24H0z" fill="none"></path>
163                                <path d="M12 20l-7 -7a4 4 0 0 1 0 -5.6a4 4 0 0 1 5.6 0l1.4 1.4l1.4 -1.4a4 4 0 0 1 5.6 0a4 4 0 0 1 0 5.6z"></path>
164                            </svg>
165                            <span>${likeCount}</span>
166                        </div>
167                    </div>
168                `;
169
170                // Assemble the final HTML for the post element
171                postElement.innerHTML = `
172                    ${repostIndicator}
173                    ${wrappedPostBody}
174                    ${reactionsHtml}
175                `;
176
177                container.appendChild(postElement);
178            });
179        })
180        .catch((error) => {
181            console.error("Error fetching feed:", error);
182            container.innerHTML =
183                "<p>Failed to fetch feed. Please check the console for details.</p>";
184        });
185}
186
187// Export function if needed in modern JS.
188if (typeof module !== "undefined") {
189    module.exports = { renderFeed };
190}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.