Release of v3.2.5
Add [AllowDynamicProperties] in the base view class for J5. Move the _prepareDocument above the display call in the base view class. Remove all backward compatibility issues, so JCB will not need the [Backward Compatibility] plugin to run. Added new import powers for custom import of spreadsheets. Move the setDocument and _prepareDocument above the display in the site view and custom admin view. Update the trashhelper layout to work in Joomla 5. Add AllowDynamicProperties (Joomla 4+5) to view class to allow Custom Dynamic Get methods to work without issues. Fix Save failed issue in dynamicGet. #1148. Move all [TEXT, EDITOR, TEXTAREA] fields from [NOT NULL] to [NULL]. Add the DateHelper class and improve the date methods. Add simple SessionHelper class. Add first classes for the new import engine. Improve the [VDM Registry] to be Joomla Registry Compatible. Move all registries to the [VDM Registry] class. Fix Checked Out to be null and not 0. (#1194). Fix created_by, modified_by, checked_out fields in the compiler of the SQL. (#1194). Update all core date fields in table class. (#1188). Update created_by, modified_by, checked_out fields in table class. Implementation of the decentralized Super-Power CORE repository network. (#1190). Fix the noticeboard to display Llewellyn's Joomla Social feed.
This commit is contained in:
302
admin/assets/js/compiler.js
Normal file
302
admin/assets/js/compiler.js
Normal file
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* @package Joomla.Component.Builder
|
||||
*
|
||||
* @created 30th April, 2015
|
||||
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
||||
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
|
||||
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
/* JS Document */
|
||||
class MastodonFeed {
|
||||
constructor(containerId, refreshButtonId) {
|
||||
this.container = document.getElementById(containerId);
|
||||
this.refreshButton = document.getElementById(refreshButtonId);
|
||||
|
||||
// Get settings from data attributes
|
||||
this.mastodonInstance = this.container.dataset.instance;
|
||||
this.accountId = this.container.dataset.accountId;
|
||||
this.postCount = parseInt(this.container.dataset.postCount) || 5;
|
||||
|
||||
this.cacheKey = "mastodon-feed-cache";
|
||||
this.cacheExpiration = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
||||
|
||||
// Initialize the feed
|
||||
this.initFeed();
|
||||
|
||||
// Attach event listener for the refresh button
|
||||
this.refreshButton.addEventListener("click", () => this.clearCacheAndReload());
|
||||
}
|
||||
|
||||
async initFeed() {
|
||||
const cachedData = this.getCachedData();
|
||||
|
||||
if (cachedData) {
|
||||
this.renderFeed(cachedData);
|
||||
} else {
|
||||
await this.loadFeed();
|
||||
}
|
||||
}
|
||||
|
||||
getCachedData() {
|
||||
const cache = localStorage.getItem(this.cacheKey);
|
||||
if (!cache) return null;
|
||||
|
||||
const parsedCache = JSON.parse(cache);
|
||||
const now = new Date().getTime();
|
||||
|
||||
if (now - parsedCache.timestamp > this.cacheExpiration) {
|
||||
// Cache is expired
|
||||
this.clearCache();
|
||||
return null;
|
||||
}
|
||||
|
||||
return parsedCache.data;
|
||||
}
|
||||
|
||||
setCachedData(data) {
|
||||
const cache = {
|
||||
timestamp: new Date().getTime(),
|
||||
data: data,
|
||||
};
|
||||
localStorage.setItem(this.cacheKey, JSON.stringify(cache));
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
localStorage.removeItem(this.cacheKey);
|
||||
}
|
||||
|
||||
async loadFeed() {
|
||||
try {
|
||||
const response = await fetch(`${this.mastodonInstance}/api/v1/accounts/${this.accountId}/statuses?limit=${this.postCount}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch Mastodon feed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const posts = await response.json();
|
||||
this.setCachedData(posts); // Cache the data
|
||||
this.renderFeed(posts);
|
||||
} catch (error) {
|
||||
console.error("Error loading Mastodon feed:", error);
|
||||
this.container.innerHTML = `<div class="alert alert-danger">Error loading feed. Please try again later.</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
renderFeed(posts) {
|
||||
// Clear existing content
|
||||
this.container.innerHTML = "";
|
||||
|
||||
posts.forEach(post => {
|
||||
if (!post.content) return; // Ignore posts with no content
|
||||
|
||||
// Create post element
|
||||
const listItem = document.createElement("div");
|
||||
listItem.className = "well well-small";
|
||||
|
||||
const postContent = document.createElement("div");
|
||||
|
||||
const user = post.account;
|
||||
const avatar = user.avatar_static;
|
||||
const displayName = user.display_name || user.username;
|
||||
|
||||
// User header
|
||||
const header = document.createElement("div");
|
||||
header.className = "media";
|
||||
|
||||
const avatarLink = document.createElement("a");
|
||||
avatarLink.href = user.url;
|
||||
avatarLink.className = "pull-left";
|
||||
|
||||
const avatarImg = document.createElement("img");
|
||||
avatarImg.src = avatar;
|
||||
avatarImg.alt = displayName;
|
||||
avatarImg.className = "img-circle";
|
||||
avatarImg.style.width = "40px";
|
||||
|
||||
avatarLink.appendChild(avatarImg);
|
||||
|
||||
const userInfo = document.createElement("div");
|
||||
userInfo.className = "media-body";
|
||||
|
||||
const nameLink = document.createElement("a");
|
||||
nameLink.href = user.url;
|
||||
nameLink.className = "muted";
|
||||
nameLink.textContent = displayName;
|
||||
|
||||
const username = document.createElement("span");
|
||||
username.className = "small muted";
|
||||
username.textContent = ` (@${user.username})`;
|
||||
|
||||
const userHeader = document.createElement("h5");
|
||||
userHeader.appendChild(nameLink);
|
||||
userHeader.appendChild(username);
|
||||
|
||||
userInfo.appendChild(userHeader);
|
||||
|
||||
header.appendChild(avatarLink);
|
||||
header.appendChild(userInfo);
|
||||
|
||||
// Post content
|
||||
const content = document.createElement("p");
|
||||
content.innerHTML = post.content;
|
||||
|
||||
// Interactions
|
||||
const interactions = document.createElement("div");
|
||||
interactions.className = "btn-group";
|
||||
|
||||
// View Post link
|
||||
const viewPost = document.createElement("a");
|
||||
viewPost.href = post.url;
|
||||
viewPost.className = "btn btn-small btn-primary";
|
||||
viewPost.innerHTML = `View Post
|
||||
<i class="icon-comments"></i> ${post.replies_count}
|
||||
<i class="icon-thumbs-up"></i> ${post.favourites_count}
|
||||
<i class="icon-loop"></i> ${post.reblogs_count}`;
|
||||
interactions.appendChild(viewPost);
|
||||
|
||||
// Join Me link
|
||||
const joinLink = document.createElement("a");
|
||||
joinLink.href = "https://joomla.social/invite/gzAvC48K";
|
||||
joinLink.className = "btn btn-small btn-success";
|
||||
joinLink.textContent = "Join Me";
|
||||
interactions.appendChild(joinLink);
|
||||
|
||||
// Assemble post
|
||||
postContent.appendChild(header);
|
||||
postContent.appendChild(content);
|
||||
postContent.appendChild(interactions);
|
||||
|
||||
listItem.appendChild(postContent);
|
||||
this.container.appendChild(listItem);
|
||||
this.container.classList.remove('loading');
|
||||
});
|
||||
}
|
||||
|
||||
clearCacheAndReload() {
|
||||
// Add spinning effect to the refresh button
|
||||
this.refreshButton.classList.add('spinning');
|
||||
|
||||
// Show placeholder content
|
||||
this.container.classList.add('loading');
|
||||
this.container.innerHTML = this.generatePlaceholder();
|
||||
|
||||
// Clear cache and reload feed
|
||||
this.clearCache();
|
||||
|
||||
// Wait for 3 seconds
|
||||
setTimeout(() => {
|
||||
// Enlarge and fade out the refresh button
|
||||
this.refreshButton.classList.add('enlarge-and-disappear');
|
||||
|
||||
// After the animation, reset the button and content
|
||||
setTimeout(() => {
|
||||
this.refreshButton.classList.remove('spinning', 'enlarge-and-disappear');
|
||||
this.refreshButton.style.display = '';
|
||||
|
||||
// Remove placeholder and restore actual content
|
||||
this.loadFeed();
|
||||
}, 1000); // Animation time for fade-out
|
||||
}, 3000); // Spinning duration
|
||||
}
|
||||
|
||||
generatePlaceholder() {
|
||||
let placeholders = '';
|
||||
for (let i = 0; i < this.postCount; i++) {
|
||||
placeholders += `
|
||||
<div class="placeholder">
|
||||
<div class="placeholder-circle"></div>
|
||||
<div class="placeholder-line"></div>
|
||||
<div class="placeholder-line"></div>
|
||||
<div class="placeholder-line"></div>
|
||||
<div class="placeholder-line"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return placeholders;
|
||||
}
|
||||
}
|
||||
|
||||
class IconWaveAnimator {
|
||||
constructor(containerId, detailsId) {
|
||||
this.details = document.getElementById(detailsId);
|
||||
this.container = document.getElementById(containerId);
|
||||
this.icons = this.container.querySelectorAll("i");
|
||||
this.links = this.container.querySelectorAll("a");
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// set the icon hover events
|
||||
this.setupHoverEvents();
|
||||
|
||||
// Random chance to do nothing (1 out of 10)
|
||||
if (Math.random() < 0.1) return;
|
||||
|
||||
// Randomize the initial delay before starting
|
||||
const initialDelay = Math.random() * 2000 + 2000; // 2–4 seconds
|
||||
setTimeout(() => {
|
||||
this.randomBehavior();
|
||||
}, initialDelay);
|
||||
|
||||
// Occasionally trigger a second wave after 10 seconds
|
||||
if (Math.random() > 0.5) {
|
||||
setTimeout(() => {
|
||||
this.mexicanWave(false); // Reverse wave
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
mexicanWave(forward = true) {
|
||||
let delay = 0;
|
||||
const iconsArray = Array.from(this.icons);
|
||||
|
||||
(forward ? iconsArray : iconsArray.reverse()).forEach((icon) => {
|
||||
setTimeout(() => {
|
||||
icon.style.transition = "transform 0.3s ease-in-out";
|
||||
icon.style.transform = "scale(1.3)";
|
||||
setTimeout(() => {
|
||||
icon.style.transform = "scale(1)";
|
||||
}, 300);
|
||||
}, delay);
|
||||
delay += 150; // Stagger the effect for the wave
|
||||
});
|
||||
}
|
||||
|
||||
randomBehavior() {
|
||||
const waveDirection = Math.random() > 0.5 ? "forward" : "backward";
|
||||
const waveCount = Math.floor(Math.random() * 10) + 1; // 1 to 5 waves
|
||||
const interval = Math.random() * 2000 + 3000; // 3 to 5 seconds
|
||||
|
||||
let executedCount = 0;
|
||||
const intervalId = setInterval(() => {
|
||||
if (executedCount >= waveCount) {
|
||||
clearInterval(intervalId);
|
||||
return;
|
||||
}
|
||||
this.mexicanWave(waveDirection === "forward");
|
||||
executedCount++;
|
||||
}, interval);
|
||||
}
|
||||
|
||||
setupHoverEvents() {
|
||||
this.links.forEach((link) => {
|
||||
link.addEventListener("mouseenter", () => this.showDetails(link));
|
||||
link.addEventListener("mouseleave", () => this.clearDetails());
|
||||
});
|
||||
}
|
||||
|
||||
showDetails(link) {
|
||||
const description = link.dataset.description;
|
||||
if (this.details && description) {
|
||||
this.details.textContent = description;
|
||||
}
|
||||
}
|
||||
|
||||
clearDetails() {
|
||||
if (this.details) {
|
||||
this.details.textContent = "";
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user