Compare commits
No commits in common. "main" and "for-pi2" have entirely different histories.
11 changed files with 29 additions and 67 deletions
|
@ -1,10 +1,9 @@
|
|||
# Mastowall 1.1
|
||||
|
||||
Forked from [https://github.com/rstockm/mastowall](https://github.com/rstockm/mastowall)
|
||||
|
||||
Mastowall is a social wall application that displays posts from the [Mastodon](https://joinmastodon.org/) social network based on specified hashtags. It was written entirely by [ChatGPT4](https://openai.com/product/gpt-4), guided only by text prompts.
|
||||
|
||||
<img width="1348" alt="image" src="https://git.verdigado.com/NB-Public/mastowall/raw/branch/main/screenshot.jpg">
|
||||
<img width="1348" alt="image" src="https://github.com/rstockm/mastowall/assets/3195116/7060536e-4847-4e38-801e-3c0312b8b194">
|
||||
|
||||
|
||||
Try it live: [Mastowall for BDK23](https://tretkowski.de/mastowall/?hashtags=bdk23,netzbegruenung&server=https://gruene.social)
|
||||
|
||||
|
@ -38,8 +37,6 @@ JSON config file:
|
|||
|
||||
- **Including Replies:** By default, replies are excluded from the wall. However, this behavior can be changed by setting includeReplies to true in the `config.json` file.
|
||||
|
||||
- **Configurable Overlay:** By default only the MastoWall is shown. For use with large displays eg during trade shows, conferences, booths, you can enable the Carousel with enlarged display of the 10 most recent posts. Just add `nbstand=1` to the argument in the URL.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
Mastowall is built using the following technologies:
|
||||
|
@ -66,7 +63,7 @@ Mastowall is built using the following technologies:
|
|||
|
||||
## Sharing via URL
|
||||
|
||||
Mastowall supports URL parameters to easily share specific hashtag configurations and the Mastodon server. Simply append the desired hashtags and the server URL to the URL following this format: `?hashtags=hashtag1,hashtag2,hashtag3&server=serverUrl&nbstand=0`
|
||||
Mastowall supports URL parameters to easily share specific hashtag configurations and the Mastodon server. Simply append the desired hashtags and the server URL to the URL following this format: `?hashtags=hashtag1,hashtag2,hashtag3&server=serverUrl`
|
||||
|
||||
Enjoy using Mastowall!
|
||||
|
||||
|
|
|
@ -6,10 +6,7 @@
|
|||
"refreshDuration": 30,
|
||||
"maxAge": 604800,
|
||||
"extraCards": [
|
||||
"<img src='sharepics/Slide1.png' style='max-width: 100%;max-height: 100%;vertical-align: middle;'>",
|
||||
"<img src='sharepics/Slide2.png' style='max-width: 100%;max-height: 100%;vertical-align: middle;'>",
|
||||
"<img src='sharepics/Slide3.png' style='max-width: 100%;max-height: 100%;vertical-align: middle;'>",
|
||||
"<img src='sharepics/Slide4.png' style='max-width: 100%;max-height: 100%;vertical-align: middle;'>"
|
||||
"<img src='sharepic.jpg' style='max-width: 100%;max-height: 100%;vertical-align: middle;'>"
|
||||
],
|
||||
"includeReplies": true
|
||||
}
|
||||
|
|
|
@ -38,11 +38,11 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label for="hashtag2">Hashtag 2:</label>
|
||||
<input type="text" class="form-control" id="hashtag2" placeholder="Enter a hashtag" value="bdk24">
|
||||
<input type="text" class="form-control" id="hashtag2" placeholder="Enter a hashtag" value="bdk23">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="hashtag3">Hashtag 3:</label>
|
||||
<input type="text" class="form-control" id="hashtag3" placeholder="Enter a hashtag" value="wasjetztzählt">
|
||||
<input type="text" class="form-control" id="hashtag3" placeholder="Enter a hashtag">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="serverUrl">Server:</label>
|
||||
|
|
BIN
screenshot.jpg
BIN
screenshot.jpg
Binary file not shown.
Before Width: | Height: | Size: 410 KiB |
68
script.js
68
script.js
|
@ -55,8 +55,6 @@ let duration;
|
|||
let refresh;
|
||||
// extra cards text
|
||||
let extraCards;
|
||||
// toggle Carousel
|
||||
let withCarousel=false;
|
||||
|
||||
// fetchConfig fetches the configuration from the config.json file
|
||||
const fetchConfig = async function() {
|
||||
|
@ -79,10 +77,7 @@ const fetchConfig = async function() {
|
|||
duration = 10000;
|
||||
refresh = 30000;
|
||||
extraCards = [
|
||||
"<img src='sharepics/Slide1.png' style='max-width: 100%;max-height: 100%;vertical-align: middle;'>",
|
||||
"<img src='sharepics/Slide2.png' style='max-width: 100%;max-height: 100%;vertical-align: middle;'>",
|
||||
"<img src='sharepics/Slide3.png' style='max-width: 100%;max-height: 100%;vertical-align: middle;'>",
|
||||
"<img src='sharepics/Slide4.png' style='max-width: 100%;max-height: 100%;vertical-align: middle;'>"
|
||||
"<div><img src='sharepic.jpg' style='max-width: 100%;max-height: 100%'></div>"
|
||||
];
|
||||
return "https://gruene.social";
|
||||
}
|
||||
|
@ -109,14 +104,6 @@ const updateTimesOnPage = function() {
|
|||
});
|
||||
};
|
||||
|
||||
// replace certain emojies in some text with images
|
||||
const replaceEmojies = (text, emojis) => {
|
||||
emojis.forEach(emoji => {
|
||||
text = text.replaceAll(`:${emoji.shortcode}:`, `<img class="emoji" src="${emoji.static_url}">`);
|
||||
});
|
||||
return text;
|
||||
};
|
||||
|
||||
// displayPost creates and displays a post
|
||||
const displayPost = function(post) {
|
||||
if (existingPosts.includes(post.id) || (!includeReplies && post.in_reply_to_id !== null)) return 0;
|
||||
|
@ -128,14 +115,10 @@ const displayPost = function(post) {
|
|||
<div class="card m-2 p-2">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<img src="${post.account.avatar}" class="avatar-img rounded-circle mr-2">
|
||||
<p class="m-0">${replaceEmojies(DOMPurify.sanitize(post.account.display_name), post.account.emojis)} <span class="user-name">@${DOMPurify.sanitize(post.account.acct)}</span></p>
|
||||
<p class="m-0">${DOMPurify.sanitize(post.account.display_name)} <span class="user-name">@${DOMPurify.sanitize(post.account.acct)}</span></p>
|
||||
</div>
|
||||
${post.media_attachments[0] ?
|
||||
(post.media_attachments[0].url.endsWith('.mp4') ?
|
||||
`<video src="${post.media_attachments[0].url}" controls autoplay muted loop></video>` :
|
||||
`<img src="${post.media_attachments[0].url}" class="card-img-top mb-2">`) :
|
||||
''}
|
||||
<p class="card-text">${replaceEmojies(DOMPurify.sanitize(post.content), post.emojis)}</p>
|
||||
${post.media_attachments[0] ? `<img src="${post.media_attachments[0].url}" class="card-img-top mb-2">` : ''}
|
||||
<p class="card-text">${DOMPurify.sanitize(post.content)}</p>
|
||||
${post.spoiler_text ? `<p class="card-text text-muted spoiler">${DOMPurify.sanitize(post.spoiler_text)}</p>` : ''}
|
||||
<p class="card-text text-right"><small class="text-muted"><a class="time" href="${post.url}" target="_blank" data-time="${post.created_at}">${timeAgo(secondsAgo(new Date(post.created_at)))}</a></small></p>
|
||||
</div>
|
||||
|
@ -150,7 +133,7 @@ const displayPost = function(post) {
|
|||
|
||||
const processPosts = function(posts) {
|
||||
posts = posts.filter((post) => {
|
||||
return secondsAgo(new Date(post.created_at)) < maxAge && post.content.indexOf("nitter.") === -1 && post.spoiler_text === ""
|
||||
return secondsAgo(new Date(post.created_at)) < maxAge && post.content.indexOf("nitter.") === -1
|
||||
});
|
||||
|
||||
return posts;
|
||||
|
@ -202,22 +185,17 @@ const updateCarousel = function(slides, posts) {
|
|||
else {
|
||||
newHTML += `<div class="carousel-item" data-interval="${duration}" data-pause="false">`;
|
||||
}
|
||||
const postContent = replaceEmojies(DOMPurify.sanitize(post.content), post.emojis);
|
||||
const postContent = DOMPurify.sanitize(post.content);
|
||||
newHTML += `
|
||||
<div class="card-big">
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<img src="${post.account.avatar}" class="avatar-img-big rounded-circle mr-4">
|
||||
<p class="avatar-name">${replaceEmojies(DOMPurify.sanitize(post.account.display_name), post.account.emojis)} <span class="user-name">@${DOMPurify.sanitize(post.account.acct)}</span></p>
|
||||
<p class="avatar-name">${DOMPurify.sanitize(post.account.display_name)} <span class="user-name">@${DOMPurify.sanitize(post.account.acct)}</span></p>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row align-items-center vertical-align-center">
|
||||
<div class="${post.media_attachments[0] ? `col-md-6` : `col-md-12`}"><div class="card-text" ${strip(postContent).length > 700 ? `style="font-size: ${strip(postContent).length > 1000 ? `0.5`:`0.9`}em;"`: ``}>${postContent}</div></div>
|
||||
${post.media_attachments[0] ?
|
||||
(post.media_attachments[0].url.endsWith('.mp4') ?
|
||||
`<div class="col-md-6"><video src="${post.media_attachments[0].url}" controls autoplay muted loop class="card-img-bottom" align="center"></video></div>` :
|
||||
`<div class="col-md-6"><img src="${post.media_attachments[0].url}" class="card-img-bottom" align="center"> </div>`) :
|
||||
''}
|
||||
|
||||
<div class="${post.media_attachments[0] ? `col-md-6` : `col-md-12`}"><div class="card-text" ${strip(postContent).length > 800 ? `style="font-size: 0.9em;"`: ``}>${postContent}</div></div>
|
||||
${post.media_attachments[0] ? `<div class="col-md-6"><img src="${post.media_attachments[0].url}" class="card-img-bottom" align="center"> </div>` : ''}
|
||||
</div>
|
||||
<hr>
|
||||
<p class="card-text text-right">
|
||||
|
@ -232,7 +210,7 @@ const updateCarousel = function(slides, posts) {
|
|||
newHTML += '</div>';
|
||||
}
|
||||
for( let i = 0; i < extraCards.length; i++ ) {
|
||||
newHTML += `<div class="carousel-item" data-interval="${duration * 2}" data-pause="false">
|
||||
newHTML += `<div class="carousel-item" data-mdb-interval="${duration * 2}" data-mdb-pause="false">
|
||||
<div className="card-big">
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
${extraCards[i]}
|
||||
|
@ -247,7 +225,6 @@ const updateCarousel = function(slides, posts) {
|
|||
const showCarousel = function() {
|
||||
// show popover
|
||||
document.getElementById('popover').style.opacity = '1';
|
||||
document.getElementById('popover').style.display = 'block';
|
||||
// Activate Carousel
|
||||
$('#myCarousel').carousel("cycle");
|
||||
}
|
||||
|
@ -258,8 +235,8 @@ const strip = function(html) {
|
|||
}
|
||||
|
||||
const hideCarousel = function() {
|
||||
// hide popover
|
||||
document.getElementById('popover').style.display = 'none';
|
||||
// show popover
|
||||
document.getElementById('popover').style.opacity = '0';
|
||||
// Activate Carousel
|
||||
}
|
||||
|
||||
|
@ -286,7 +263,7 @@ const handleHashtagDisplayClick = function(serverUrl) {
|
|||
|
||||
const currentHashtags = getUrlParameter('hashtags').split(',');
|
||||
if ( currentHashtags = null ) {
|
||||
currentHasttags = "netzbegruenung,bdk24,wasjetztzählt".split(',')
|
||||
currentHasttags = "netzbegruenung,bdk23".split(',')
|
||||
}
|
||||
|
||||
for (let i = 0; i < currentHashtags.length; i++) {
|
||||
|
@ -337,16 +314,11 @@ $(document).ready(async function() {
|
|||
|
||||
let hashtags = getUrlParameter('hashtags');
|
||||
if ( hashtags == '' ) {
|
||||
hashtags = "netzbegruenung,bdk24,wasjetztzählt";
|
||||
hashtags = "netzbegruenung,bdk23";
|
||||
}
|
||||
const hashtagsArray = hashtags ? hashtags.split(',') : [];
|
||||
const serverUrl = getUrlParameter('server') || defaultServerUrl;
|
||||
|
||||
const enableCarousel = getUrlParameter('nbstand' );
|
||||
if ( enableCarousel == '1' )
|
||||
withCarousel = true;
|
||||
console.log("show carousel: "+withCarousel);
|
||||
|
||||
$('#hashtag-display').on('click', function() {
|
||||
handleHashtagDisplayClick(serverUrl);
|
||||
});
|
||||
|
@ -362,16 +334,16 @@ $(document).ready(async function() {
|
|||
$('.masonry-grid').masonry('layout');
|
||||
}, 2000);
|
||||
|
||||
if ( withCarousel) {
|
||||
updateCarousel(slides, allPosts.flat());
|
||||
setTimeout(async function() { showCarousel(); }, duration);
|
||||
}
|
||||
else setTimeout(async function() { hideCarousel(); }, duration);
|
||||
updateCarousel(slides, allPosts.flat());
|
||||
|
||||
setTimeout(async function() { showCarousel(); }, duration)
|
||||
setInterval(async function() {
|
||||
const newPosts = await Promise.all(hashtagsArray.map(hashtag => fetchPosts(serverUrl, hashtag)));
|
||||
let updated = updateWall(newPosts.flat());
|
||||
if ( withCarousel && updated > 0 ) {
|
||||
if ( updated > 0 ) {
|
||||
hideCarousel()
|
||||
updateCarousel(slides, newPosts.flat());
|
||||
setTimeout(async function() { showCarousel(); }, duration)
|
||||
}
|
||||
}, refresh);
|
||||
} else {
|
||||
|
|
BIN
sharepic.jpg
Normal file
BIN
sharepic.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 129 KiB |
Binary file not shown.
Before Width: | Height: | Size: 68 KiB |
Binary file not shown.
Before Width: | Height: | Size: 77 KiB |
Binary file not shown.
Before Width: | Height: | Size: 101 KiB |
Binary file not shown.
Before Width: | Height: | Size: 56 KiB |
10
styles.css
10
styles.css
|
@ -51,9 +51,9 @@
|
|||
}
|
||||
|
||||
.carousel .card-text {
|
||||
font-size: 1.6em;
|
||||
font-size: 1.4em;
|
||||
overflow: hidden;
|
||||
max-height: 50vh;
|
||||
max-height: 60vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
|
@ -208,7 +208,7 @@ body {
|
|||
|
||||
.text-muted {
|
||||
color:#6c757d !important;
|
||||
font-size: 1em;
|
||||
font-size: 1.2em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
@ -224,7 +224,3 @@ body {
|
|||
.footer a {
|
||||
color: rgba(255, 255, 255, 0.8) !important;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
height: 1em;
|
||||
}
|
Loading…
Reference in a new issue