647 lines
24 KiB
HTML
647 lines
24 KiB
HTML
<meta charset="utf-8">
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<script>
|
|
$(document).ready(function () {
|
|
const federatedHandle = "alceawis@alceawis.com";
|
|
const searchURL = `https://mas.to/search?q=${encodeURIComponent(federatedHandle)}&type=accounts`;
|
|
const container = $('<div>').addClass('formClass');
|
|
const paragraph = $('<p>').text("You can follow this profile on Fediverse by typing the handle in your instance search or clicking this link:");
|
|
container.append(paragraph);
|
|
const link = $('<a>')
|
|
.attr('href', searchURL)
|
|
.attr('target', '_blank')
|
|
.text(federatedHandle);
|
|
container.append(link);
|
|
$('body').prepend(container);
|
|
});
|
|
</script>
|
|
|
|
<a target="_blank" href="https://mas.to/search?q=alceawis%40alceawis.com&type=accounts" style=color:pink>2</a><br>
|
|
(Or use this <a target="_blank" href="https://mastodon.social/@alcea/114834785762248316" style=color:gray>post</a> as reference to find the profile. You can search for this url too!) <a target="_blank" href="https://alceawis.de/other/extra/scripts/fakesocialmedia/commentload.html?number=4000&text=Where%20%40alceawis%40alceawis." id=3936 style=color:green>Instance Compatibility</a><hr>
|
|
<!-----Debug---->
|
|
<details><summary>Debug</summary>
|
|
<a target="_blank" href="debug.log" style=color:blue>debug.log</a><br>
|
|
<a target="_blank" href="inbox.log" style=color:blue>inbox.log</a><br>
|
|
<a target="_blank" href="https://alceawis.com/emojifetch.php" style=color:blue>Emoji Downloader</a><br>
|
|
*<a href="https://alceawis.com/api/v1/custom_emojis" target="_blank" rel="noopener noreferrer">Emoji Endpoint</a><br>
|
|
*<a href="https://alceawis.com/api/v1/statuses/20250713-f441340a" target="_blank" rel="noopener noreferrer">v1 Statuses API</a><br>
|
|
*<a href="https://alceawis.com/alceawis/followers" target="_blank" rel="noopener noreferrer">Followers</a><br>
|
|
*<a href="https://alceawis.com/alceawis/outbox?page=true" target="_blank" rel="noopener noreferrer">User Timeline (Outbox, paged)</a><br>
|
|
<hr>
|
|
<div id="debug-info"></div>
|
|
</details>
|
|
<!--Profile-->
|
|
<div style="
|
|
position: fixed;
|
|
left: 0;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
padding: 20px;
|
|
background-color: #fff;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
border-radius: 10px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;">
|
|
<div style="width: 120px; height: 120px; padding: 0px; border: 2px solid #ccc; border-radius: 50%; overflow: hidden; margin-bottom: 20px;">
|
|
<img src="/z_files/emojis/alceawis.gif" alt="Image" style="width: 100%; height: 100%; object-fit: cover;">
|
|
</div>
|
|
<div style="text-align: center;">
|
|
<p>Hi I'm <a target="_blank" href="https://alceawis.de" style=color:pink>Alcea</a>'s Relay Page</p>
|
|
<p>This is a federation site for</p>
|
|
<p> <a target="_blank" href="https://alceawis.de/about" style=color:pink>my site</a>'s timeline</p>
|
|
<hr>
|
|
<div id="followerCount"></div><script>fetch('https://alceawis.com/alceawis/followers').then(res => res.json()).then(data => document.getElementById('followerCount').innerHTML = `<a href="https://alceawis.com/reportfollowers.php#https://alceawis.com/alceawis/followers" target="_blank" style="color:pink;">Followers: ${data.orderedItems.length}</a>`).catch(err => console.error('Error:', err));</script>
|
|
<div id="postCount"></div><script>fetch('https://alceawis.com/alceawis/outbox').then(res => res.json()).then(data => document.getElementById('postCount').innerHTML = `<a href="#" target="_blank" style="color:pink;">Posts: ${data.totalItems}</a>`).catch(err => console.error('Error:', err));</script>
|
|
<form id="lookup-form"><input type="text" id="domain" name="domain" placeholder="Enter domain" value="mas.to"><br><input type="text" id="user" name="user" placeholder="Enter username" value="@alceawis@alceawis.com"><button type="button" onclick="openLookup()">Go</button></form><script>function openLookup() {const domain = document.getElementById('domain').value.trim();const user = document.getElementById('user').value.trim();if (domain && user) {const url = `https://${domain}/api/v1/accounts/lookup?acct=${user}`;window.open(url, '_blank');} else {alert("Please fill out both fields.");}}</script>
|
|
|
|
<!--<a target="_blank" href="#" style="display: block; margin: 5px; color: #D5006D; text-decoration: none;">-</a>-->
|
|
<hr>
|
|
<a target="_blank" href="https://app.pulsetic.com/status/bVlNm1Cj/?preview=true#https://stats.uptimerobot.com/6wi9pgxFJ5" style=color:#D5006D>Status</a><br>
|
|
<a target="_blank" href="https://fba.ryona.agency/?domain=alceawis.com" style=color:#D5006D>BLCK</a>(<a target="_blank" href="https://alcea-wisteria.de/PHP/0demo/2023-08-15-JSFiddle-Clone/htmls/2025-07-20-Fediverse-AmISuspended-Checker.html#https://codepen.io/ryedai1/full/wBKMxBJ" style=color:orange>Suspended ?</a>)<br>
|
|
<a target="_blank" href="https://mastodon.social/@alcea" style="display: block; margin: 5px; color: #D5006D; text-decoration: none;">@alcea@mastodon.social</a>
|
|
<a target="_blank" href="https://alcea-wisteria.de/PHP/0demo/2024-03-07-FediTools/2025-07-12-Post-JSON-Fetch/fetch.php" style=color:#D5006D>JSON Post Test</a>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Alcea's Timeline</title>
|
|
<style>
|
|
@keyframes colorCycle {
|
|
0% {
|
|
color: #ff69b4; /* Light Pink */
|
|
}
|
|
50% {
|
|
color: #d5006c; /* Dark Pink */
|
|
}
|
|
100% {
|
|
color: #ff69b4; /* Light Pink */
|
|
}
|
|
}
|
|
h1 {
|
|
animation: colorCycle 5s infinite ease-in-out;
|
|
}
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
max-width: 800px;
|
|
margin: auto;
|
|
padding: 20px;
|
|
}
|
|
.post {
|
|
border: 1px solid #ccc;
|
|
padding: 15px;
|
|
margin: 10px 0;
|
|
border-radius: 8px;
|
|
background-color: #FFB6C1;
|
|
}
|
|
.post .content {
|
|
margin-bottom: 10px;
|
|
}
|
|
.post img {
|
|
max-height: 45px;
|
|
}
|
|
.emoji {
|
|
width: 45px;
|
|
height: 45px;
|
|
}
|
|
a {
|
|
color: #D5006D;
|
|
text-decoration: none;
|
|
}
|
|
a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
.interactions {
|
|
margin-top: 10px;
|
|
font-size: 0.95em;
|
|
background-color: #f9f9f9;
|
|
padding: 8px;
|
|
border-radius: 5px;
|
|
}
|
|
.interactions strong {
|
|
display: block;
|
|
margin-bottom: 4px;
|
|
}
|
|
ul {
|
|
padding-left: 20px;
|
|
margin: 5px 0;
|
|
}
|
|
li {
|
|
margin-bottom: 5px;
|
|
}
|
|
.reply-container {
|
|
margin-top: 8px;
|
|
border-left: 2px solid #ccc;
|
|
padding-left: 10px;
|
|
}
|
|
.reply {
|
|
background-color: #f0f2f5;
|
|
border-radius: 8px;
|
|
padding: 10px;
|
|
margin: 8px 0;
|
|
font-size: 0.9em;
|
|
transition: background-color 0.2s ease-in-out;
|
|
}
|
|
.reply:hover {
|
|
background-color: #e8ebf0;
|
|
}
|
|
.reply-meta {
|
|
font-weight: bold;
|
|
color: #333;
|
|
margin-bottom: 4px;
|
|
}
|
|
.reply-content {
|
|
margin-bottom: 6px;
|
|
}
|
|
.timestamp {
|
|
color: #666;
|
|
font-weight: normal;
|
|
font-size: 0.85em;
|
|
}
|
|
.reply-link {
|
|
font-size: 0.85em;
|
|
color: #0073e6;
|
|
text-decoration: none;
|
|
}
|
|
.reply-link:hover {
|
|
text-decoration: underline;
|
|
}
|
|
.green-link {
|
|
color: green;
|
|
}
|
|
#debug-info {
|
|
margin-top: 20px;
|
|
background-color: #f0f0f0;
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
font-family: monospace;
|
|
font-size: 0.9em;
|
|
color: red;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Alcea's Timeline</h1>
|
|
[<a href="/" style="color:red">...</a>]
|
|
<div id="timeline"></div>
|
|
<button id="loadMoreBtn" style="display:none; margin-top: 20px;">Load More</button>
|
|
<script>
|
|
(function(){
|
|
const debugDiv = document.getElementById("debug-info");
|
|
|
|
function logToDOM(message, type = "log") {
|
|
if (!debugDiv) return;
|
|
const line = document.createElement("div");
|
|
line.textContent = message;
|
|
if (type === "error") line.style.color = "red";
|
|
debugDiv.appendChild(line);
|
|
}
|
|
|
|
const originalConsoleLog = console.log;
|
|
const originalConsoleError = console.error;
|
|
|
|
console.log = function(...args) {
|
|
originalConsoleLog.apply(console, args);
|
|
logToDOM(args.join(" "), "log");
|
|
};
|
|
|
|
console.error = function(...args) {
|
|
originalConsoleError.apply(console, args);
|
|
logToDOM(args.join(" "), "error");
|
|
};
|
|
})();
|
|
|
|
const apiUrl = "https://alceawis.com/alceawis/outbox?page=true";
|
|
const interactionUrl = `interaction.json?t=${Date.now()}`;
|
|
const pushedJsonUrl = `pushed.json?t=${Date.now()}`;
|
|
let allPosts = [];
|
|
let allInteractions = [];
|
|
let pushedLinks = [];
|
|
let currentIndex = 0;
|
|
const initialLoad = 20;
|
|
const loadIncrement = 15;
|
|
|
|
const filterUrl = window.location.hash ? window.location.hash.substring(1) : null;
|
|
|
|
function logToDOM(message) {
|
|
const debugDiv = document.getElementById("debug-info");
|
|
const line = document.createElement("div");
|
|
line.textContent = `[debug] ${message}`;
|
|
debugDiv.appendChild(line);
|
|
}
|
|
|
|
async function fetchTimelineAndInteractions() {
|
|
try {
|
|
const postRes = await fetch(apiUrl);
|
|
const interactionRaw = await fetch(interactionUrl).then(r => r.text());
|
|
const pushedRaw = await fetch(pushedJsonUrl).then(r => r.text());
|
|
|
|
logToDOM(`Fetched interaction.json (first 200 chars): ${interactionRaw.substring(0, 200)}`);
|
|
logToDOM(`Fetched pushed.json (first 200 chars): ${pushedRaw.substring(0, 200)}`);
|
|
|
|
let postData = await postRes.json();
|
|
logToDOM(`Fetched posts: ${postData.orderedItems?.length || 0}`);
|
|
|
|
let interactionData = [];
|
|
let pushedData = [];
|
|
|
|
try {
|
|
interactionData = JSON.parse(interactionRaw);
|
|
logToDOM("Parsed interaction.json successfully.");
|
|
} catch (e) {
|
|
logToDOM(`❌ Failed to parse interaction.json: ${e.message}`);
|
|
}
|
|
|
|
try {
|
|
pushedData = JSON.parse(pushedRaw);
|
|
logToDOM("Parsed pushed.json successfully.");
|
|
} catch (e) {
|
|
logToDOM(`❌ Failed to parse pushed.json: ${e.message}`);
|
|
}
|
|
|
|
allPosts = postData.orderedItems || [];
|
|
allInteractions = interactionData;
|
|
pushedLinks = pushedData;
|
|
|
|
if (filterUrl) {
|
|
const filtered = allPosts.filter(post => post.id === filterUrl);
|
|
if (filtered.length > 0) {
|
|
displayTimeline(filtered, allInteractions);
|
|
} else {
|
|
document.getElementById("timeline").innerHTML = "<p>Post not found.</p>";
|
|
}
|
|
} else {
|
|
displayNextPosts(initialLoad);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error loading data:", error);
|
|
logToDOM("❌ General fetch error — check console for stack trace.");
|
|
}
|
|
}
|
|
|
|
function displayNextPosts(count) {
|
|
const nextPosts = allPosts.slice(currentIndex, currentIndex + count);
|
|
displayTimeline(nextPosts, allInteractions);
|
|
currentIndex += count;
|
|
|
|
logToDOM(`Displaying ${nextPosts.length} posts. currentIndex: ${currentIndex}, total: ${allPosts.length}`);
|
|
|
|
const loadMoreBtn = document.getElementById("loadMoreBtn");
|
|
if (currentIndex < allPosts.length) {
|
|
loadMoreBtn.style.display = "block";
|
|
logToDOM("✔️ Showed Load More button");
|
|
} else {
|
|
loadMoreBtn.style.display = "none";
|
|
logToDOM("✔️ All posts loaded. Hiding Load More button.");
|
|
}
|
|
}
|
|
|
|
document.getElementById("loadMoreBtn").addEventListener("click", () => {
|
|
logToDOM("🟢 Load More button clicked");
|
|
displayNextPosts(loadIncrement);
|
|
});
|
|
|
|
function displayTimeline(posts, interactions) {
|
|
const timelineContainer = document.getElementById("timeline");
|
|
|
|
posts.forEach((post, index) => {
|
|
const content = parseContent(post.content);
|
|
const postId = post.id;
|
|
|
|
const likes = interactions.filter(i => i.type === "Like" && i.object === postId);
|
|
const rewts = interactions.filter(i => i.type === "Announce" && i.object === postId);
|
|
const repliesHtml = collectReplies(postId, interactions);
|
|
|
|
const likesHtml = likes.length
|
|
? `<strong>Likes:</strong> ${likes.map(like =>
|
|
`❤️ <a href="${like.actor}" target="_blank">${getHandle(like.actor)}</a>`
|
|
).join(", ")}`
|
|
: "";
|
|
|
|
const rewtsHtml = rewts.length
|
|
? `<strong>Rewts:</strong> ${rewts.map(r =>
|
|
`🔁 <a href="${r.actor}" target="_blank">${getHandle(r.actor)}</a>`
|
|
).join(", ")}`
|
|
: "";
|
|
|
|
const interactionSection = (likesHtml || rewtsHtml || repliesHtml)
|
|
? `<div class="interactions">
|
|
${likesHtml}
|
|
${rewtsHtml}
|
|
${repliesHtml}
|
|
</div>`
|
|
: "";
|
|
|
|
// Create the post element (same as your original code)
|
|
const postElement = document.createElement("div");
|
|
postElement.classList.add("post");
|
|
postElement.innerHTML = `
|
|
<strong>@<a href="${post.attributedTo}" target="_blank">alceawis</a></strong><br>
|
|
<em>Published: ${new Date(post.published).toLocaleString()}</em><br>
|
|
<div class="content">${content}</div>
|
|
<a href="${post.id}" target="_blank">[link]</a>
|
|
<button class="reply-btn" data-post-id="${post.id}" style="padding:5px 10px; cursor:pointer;background: rgba(255, 105, 180, 0.7); border: none;border-radius: 12px;color: white;font-weight: bold;transition: background 0.3s ease;"> Reply</button>
|
|
<button class="json-btn" data-json-url="${post.id}" style="padding:5px 10px; cursor:pointer;background: rgba(255, 105, 180, 0.4); border: none;border-radius: 12px;color: white;font-weight: bold;transition: background 0.3s ease;"> Show JSON</button>
|
|
|
|
|
|
|
|
|
|
|
|
${interactionSection}
|
|
`;
|
|
|
|
// Only modify the first post to add the found link
|
|
if (index === 0) {
|
|
const statusUrl = `statusurl.php?url=${encodeURIComponent(post.id)}`;
|
|
|
|
// Set up a timeout for the fetch request
|
|
const timeout = 5000; // Timeout after 5 seconds
|
|
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
|
|
fetch(statusUrl, { signal: controller.signal })
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
clearTimeout(timeoutId); // Clear the timeout once we get a response
|
|
|
|
const foundLink = document.createElement("a");
|
|
foundLink.target = "_blank";
|
|
|
|
// Check if the PHP response contains a valid ID
|
|
if (data.includes("No post found") || data.includes("No matching post")) {
|
|
foundLink.textContent = "[Not Found]";
|
|
foundLink.style.color = "red";
|
|
foundLink.href = statusUrl; // Link back to the status URL
|
|
} else {
|
|
const foundId = data.trim(); // The ID returned by PHP
|
|
foundLink.textContent = "[Found]";
|
|
foundLink.style.color = "green";
|
|
foundLink.href = `https://mas.to/web/statuses/${foundId}`; // Direct link to mas.to/status
|
|
}
|
|
|
|
// Append the status link right next to the post link
|
|
const postLink = postElement.querySelector("a[href='" + post.id + "']");
|
|
postLink.insertAdjacentElement("afterend", foundLink);
|
|
})
|
|
.catch(error => {
|
|
clearTimeout(timeoutId); // Clear the timeout in case of failure
|
|
|
|
const foundLink = document.createElement("a");
|
|
foundLink.target = "_blank";
|
|
foundLink.textContent = "[Service Unavailable]";
|
|
foundLink.href = statusUrl;
|
|
foundLink.style.color = "gray"; // Indicate service error
|
|
|
|
// Append the error message next to the post link
|
|
const postLink = postElement.querySelector("a[href='" + post.id + "']");
|
|
postLink.insertAdjacentElement("afterend", foundLink);
|
|
});
|
|
}
|
|
|
|
// Append the post element to the timeline container (unchanged from original code)
|
|
timelineContainer.appendChild(postElement);
|
|
});
|
|
|
|
compareLinksToPushed();
|
|
}
|
|
|
|
|
|
function collectReplies(postId, interactions, depth = 0) {
|
|
const replies = interactions.filter(i =>
|
|
i.type === "Create" && i.object?.inReplyTo === postId
|
|
);
|
|
|
|
return replies.map(reply => {
|
|
const actor = reply.actor || reply.object?.attributedTo;
|
|
const postUrl = reply.object?.url || "#";
|
|
const replyContent = parseContent(reply.object?.content || "");
|
|
const published = reply.object?.published
|
|
? new Date(reply.object.published).toLocaleString()
|
|
: '';
|
|
|
|
const childReplies = collectReplies(reply.object?.id, interactions, depth + 1);
|
|
|
|
return `<div class="reply" style="margin-left: ${depth * 20}px;">
|
|
<div class="reply-meta">
|
|
<a href="${actor}" target="_blank">${getHandle(actor)}</a> • <span class="timestamp">${published}</span>
|
|
</div>
|
|
<div class="reply-content">${replyContent}</div>
|
|
<a class="reply-link" href="${postUrl}" target="_blank">View reply</a>
|
|
<a class="reply-link" href="https://alceawis.de/searchmastourl.html?search=${postUrl}" target="_blank">Rslv</a>
|
|
${childReplies}
|
|
</div>`;
|
|
}).join("");
|
|
}
|
|
|
|
function compareLinksToPushed() {
|
|
const allLinks = document.querySelectorAll("a[href]");
|
|
allLinks.forEach(link => {
|
|
const postUrl = link.getAttribute("href");
|
|
if (!postUrl) return; // skip if href is null or empty
|
|
|
|
const isPushed = pushedLinks.some(linkUrl => normalizeUrl(postUrl) === normalizeUrl(linkUrl));
|
|
if (isPushed) {
|
|
link.classList.add("green-link"); // Add green class if matched
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
function parseContent(content) {
|
|
if (!content) return '';
|
|
let parsedContent = content;
|
|
|
|
parsedContent = parsedContent.replace(/#([a-zA-Z0-9_]+)/g, (match, tag) => {
|
|
return `<a href="https://alceawis.de/other/extra/scripts/fakesocialmedia/commentload.html?text=${tag}&number=4000" target="_blank">${match}</a>`;
|
|
});
|
|
|
|
parsedContent = parsedContent.replace(/:([a-zA-Z0-9_]+):/g, (match, emoji) => {
|
|
const emojiUrl = `https://alceawis.com/z_files/emojis/${emoji}.gif`;
|
|
return `<img src="${emojiUrl}" alt="${emoji}" class="emoji" />`;
|
|
});
|
|
|
|
parsedContent = parsedContent.replace(/<a href="(https:\/\/[^\s]+?\.(png|jpeg|jpg|gif))"[^>]*>/g, (match, imgUrl) => {
|
|
return `<img src="${imgUrl}" style="max-width: 50%;" />`;
|
|
});
|
|
|
|
parsedContent = parsedContent.replace(
|
|
/<a href="(https:\/\/(www|m)\.youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)(?:&[^"]*)?)"[^>]*>.*?<\/a>/g,
|
|
(match, url, subdomain, videoId) => {
|
|
if (url.includes("youtube.com/shorts")) {
|
|
return match;
|
|
}
|
|
return `
|
|
<a href="${url}" target="_blank" rel="nofollow noopener noreferrer">${url}</a>
|
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}"
|
|
frameborder="0" allowfullscreen allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture">
|
|
</iframe>`;
|
|
}
|
|
);
|
|
|
|
return parsedContent;
|
|
}
|
|
|
|
function normalizeUrl(url) {
|
|
if (url == null) {
|
|
console.error("[normalizeUrl] Received null or undefined:", url);
|
|
return "";
|
|
}
|
|
try {
|
|
return new URL(url).toString().replace(/\/+$/, ''); // remove trailing slashes
|
|
} catch {
|
|
if (typeof url === "string") {
|
|
return url.trim();
|
|
} else {
|
|
console.error("[normalizeUrl] Non-string value:", url);
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function getHandle(actorUrl) {
|
|
try {
|
|
const url = new URL(actorUrl);
|
|
const username = url.pathname.split('/').pop();
|
|
return `@${username}@${url.hostname}`;
|
|
} catch {
|
|
return actorUrl;
|
|
}
|
|
}
|
|
|
|
fetchTimelineAndInteractions();
|
|
</script>
|
|
|
|
|
|
<!-- Single shared reply modal -->
|
|
<div id="replyModal" style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.5);justify-content:center;align-items:center;z-index:1000;overflow:auto" onclick="if(event.target===this)this.style.display='none'">
|
|
<div style="background:#fff;padding:1em;border-radius:8px;max-width:400px;width:100%">
|
|
<p>Reply or interact with post <br> enter @user@domain or homedomain):</p>
|
|
<input type="text" id="modalInput" placeholder="@user@domain" style="width:calc(100% - 120px);padding:0.5em;margin-right:10px" aria-label="Enter username and domain"/>
|
|
<button id="submitReplyBtn" style="padding:0.5em 1em;background-color:#007BFF;color:white;border:none;border-radius:5px;cursor:pointer" onclick="submitReply()">Submit</button>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
let currentPostId = null;
|
|
document.addEventListener("click", function (e) {
|
|
if (e.target.matches(".reply-btn")) {
|
|
currentPostId = e.target.getAttribute("data-post-id");
|
|
document.getElementById("replyModal").style.display = "flex";
|
|
}
|
|
});
|
|
document.getElementById("submitReplyBtn").addEventListener("click", function (e) {
|
|
e.stopPropagation();
|
|
const input = document.getElementById("modalInput");
|
|
const value = input.value.trim().replace(/^@?/, "").split("@").pop();
|
|
if (value && currentPostId) {
|
|
window.open(`https://${value}/authorize_interaction?uri=${currentPostId}`, "_blank");
|
|
}
|
|
});
|
|
</script>
|
|
|
|
|
|
<!--Show--Json-->
|
|
<script>
|
|
(function() {
|
|
const modalHTML = `
|
|
<div id="jsonModal" style="
|
|
display:none;
|
|
position:fixed;
|
|
top:0; left:0;
|
|
width:100vw; height:100vh;
|
|
background:rgba(0,0,0,0.5);
|
|
justify-content:center;
|
|
align-items:center;
|
|
z-index:1000;
|
|
">
|
|
<div style="
|
|
background:#fff;
|
|
padding:1em;
|
|
max-width:90%;
|
|
max-height:80vh;
|
|
overflow:auto;
|
|
border-radius:5px;
|
|
position:relative;
|
|
width: 90%;
|
|
max-width: 600px;
|
|
" onclick="event.stopPropagation()">
|
|
<button id="copyJsonBtn" style="
|
|
position:absolute; top:10px; right:10px;
|
|
padding:5px 10px; cursor:pointer;
|
|
background: rgba(255, 105, 180, 0.7); /* pink with 70% opacity */
|
|
border: none;
|
|
border-radius: 12px; /* rounded corners */
|
|
color: white;
|
|
font-weight: bold;
|
|
transition: background 0.3s ease;
|
|
" disabled>Copy</button>
|
|
<pre id="jsonContent" style="
|
|
white-space:pre-wrap;
|
|
font-size:0.85em;
|
|
margin-top: 40px;
|
|
user-select: text;
|
|
"></pre>
|
|
</div>
|
|
</div>
|
|
`;
|
|
const tempDiv = document.createElement('div');
|
|
tempDiv.innerHTML = modalHTML;
|
|
document.body.appendChild(tempDiv.firstElementChild);
|
|
const copyBtn = document.getElementById('copyJsonBtn');
|
|
const content = document.getElementById('jsonContent');
|
|
copyBtn.addEventListener('click', () => {
|
|
if (copyBtn.disabled) return;
|
|
const textToCopy = content.textContent;
|
|
if (!textToCopy) return;
|
|
navigator.clipboard.writeText(textToCopy)
|
|
.then(() => {
|
|
copyBtn.textContent = 'Copied!';
|
|
setTimeout(() => copyBtn.textContent = 'Copy', 1500);
|
|
})
|
|
.catch(() => {
|
|
copyBtn.textContent = 'Failed to copy';
|
|
setTimeout(() => copyBtn.textContent = 'Copy', 1500);
|
|
});
|
|
});
|
|
})();
|
|
document.addEventListener('click', async (e) => {
|
|
const modal = document.getElementById('jsonModal');
|
|
const copyBtn = document.getElementById('copyJsonBtn');
|
|
const content = document.getElementById('jsonContent');
|
|
if (e.target === modal) {
|
|
modal.style.display = 'none';
|
|
return;
|
|
}
|
|
if (e.target.matches('.json-btn')) {
|
|
const url = e.target.getAttribute('data-json-url');
|
|
if (!url) {
|
|
content.textContent = '❌ Invalid JSON URL.';
|
|
modal.style.display = 'flex';
|
|
copyBtn.disabled = true;
|
|
return;
|
|
}
|
|
content.textContent = 'Loading...';
|
|
modal.style.display = 'flex';
|
|
copyBtn.disabled = true;
|
|
try {
|
|
const res = await fetch(url, { headers: { 'Accept': 'application/activity+json' } });
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
const data = await res.json();
|
|
content.textContent = JSON.stringify(data, null, 2);
|
|
copyBtn.disabled = false;
|
|
copyBtn.textContent = 'Copy';
|
|
} catch (err) {
|
|
content.textContent = `❌ Failed to load JSON:\n${err.message}`;
|
|
copyBtn.disabled = true;
|
|
copyBtn.textContent = 'Copy';
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|