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
|
# 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.
|
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)
|
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.
|
- **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
|
## Technology Stack
|
||||||
|
|
||||||
Mastowall is built using the following technologies:
|
Mastowall is built using the following technologies:
|
||||||
|
@ -66,7 +63,7 @@ Mastowall is built using the following technologies:
|
||||||
|
|
||||||
## Sharing via URL
|
## 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!
|
Enjoy using Mastowall!
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,7 @@
|
||||||
"refreshDuration": 30,
|
"refreshDuration": 30,
|
||||||
"maxAge": 604800,
|
"maxAge": 604800,
|
||||||
"extraCards": [
|
"extraCards": [
|
||||||
"<img src='sharepics/Slide1.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;'>"
|
||||||
"<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;'>"
|
|
||||||
],
|
],
|
||||||
"includeReplies": true
|
"includeReplies": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,11 +38,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="hashtag2">Hashtag 2:</label>
|
<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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="hashtag3">Hashtag 3:</label>
|
<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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="serverUrl">Server:</label>
|
<label for="serverUrl">Server:</label>
|
||||||
|
|
BIN
screenshot.jpg
BIN
screenshot.jpg
Binary file not shown.
Before Width: | Height: | Size: 410 KiB |
66
script.js
66
script.js
|
@ -55,8 +55,6 @@ let duration;
|
||||||
let refresh;
|
let refresh;
|
||||||
// extra cards text
|
// extra cards text
|
||||||
let extraCards;
|
let extraCards;
|
||||||
// toggle Carousel
|
|
||||||
let withCarousel=false;
|
|
||||||
|
|
||||||
// fetchConfig fetches the configuration from the config.json file
|
// fetchConfig fetches the configuration from the config.json file
|
||||||
const fetchConfig = async function() {
|
const fetchConfig = async function() {
|
||||||
|
@ -79,10 +77,7 @@ const fetchConfig = async function() {
|
||||||
duration = 10000;
|
duration = 10000;
|
||||||
refresh = 30000;
|
refresh = 30000;
|
||||||
extraCards = [
|
extraCards = [
|
||||||
"<img src='sharepics/Slide1.png' style='max-width: 100%;max-height: 100%;vertical-align: middle;'>",
|
"<div><img src='sharepic.jpg' style='max-width: 100%;max-height: 100%'></div>"
|
||||||
"<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;'>"
|
|
||||||
];
|
];
|
||||||
return "https://gruene.social";
|
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
|
// displayPost creates and displays a post
|
||||||
const displayPost = function(post) {
|
const displayPost = function(post) {
|
||||||
if (existingPosts.includes(post.id) || (!includeReplies && post.in_reply_to_id !== null)) return 0;
|
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="card m-2 p-2">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<img src="${post.account.avatar}" class="avatar-img rounded-circle mr-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>
|
</div>
|
||||||
${post.media_attachments[0] ?
|
${post.media_attachments[0] ? `<img src="${post.media_attachments[0].url}" class="card-img-top mb-2">` : ''}
|
||||||
(post.media_attachments[0].url.endsWith('.mp4') ?
|
<p class="card-text">${DOMPurify.sanitize(post.content)}</p>
|
||||||
`<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.spoiler_text ? `<p class="card-text text-muted spoiler">${DOMPurify.sanitize(post.spoiler_text)}</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>
|
<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>
|
</div>
|
||||||
|
@ -150,7 +133,7 @@ const displayPost = function(post) {
|
||||||
|
|
||||||
const processPosts = function(posts) {
|
const processPosts = function(posts) {
|
||||||
posts = posts.filter((post) => {
|
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;
|
return posts;
|
||||||
|
@ -202,22 +185,17 @@ const updateCarousel = function(slides, posts) {
|
||||||
else {
|
else {
|
||||||
newHTML += `<div class="carousel-item" data-interval="${duration}" data-pause="false">`;
|
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 += `
|
newHTML += `
|
||||||
<div class="card-big">
|
<div class="card-big">
|
||||||
<div class="d-flex align-items-center mb-4">
|
<div class="d-flex align-items-center mb-4">
|
||||||
<img src="${post.account.avatar}" class="avatar-img-big rounded-circle mr-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>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row align-items-center vertical-align-center">
|
<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>
|
<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] ?
|
${post.media_attachments[0] ? `<div class="col-md-6"><img src="${post.media_attachments[0].url}" class="card-img-bottom" align="center"> </div>` : ''}
|
||||||
(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>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<p class="card-text text-right">
|
<p class="card-text text-right">
|
||||||
|
@ -232,7 +210,7 @@ const updateCarousel = function(slides, posts) {
|
||||||
newHTML += '</div>';
|
newHTML += '</div>';
|
||||||
}
|
}
|
||||||
for( let i = 0; i < extraCards.length; i++ ) {
|
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 className="card-big">
|
||||||
<div class="d-flex align-items-center mb-4">
|
<div class="d-flex align-items-center mb-4">
|
||||||
${extraCards[i]}
|
${extraCards[i]}
|
||||||
|
@ -247,7 +225,6 @@ const updateCarousel = function(slides, posts) {
|
||||||
const showCarousel = function() {
|
const showCarousel = function() {
|
||||||
// show popover
|
// show popover
|
||||||
document.getElementById('popover').style.opacity = '1';
|
document.getElementById('popover').style.opacity = '1';
|
||||||
document.getElementById('popover').style.display = 'block';
|
|
||||||
// Activate Carousel
|
// Activate Carousel
|
||||||
$('#myCarousel').carousel("cycle");
|
$('#myCarousel').carousel("cycle");
|
||||||
}
|
}
|
||||||
|
@ -258,8 +235,8 @@ const strip = function(html) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideCarousel = function() {
|
const hideCarousel = function() {
|
||||||
// hide popover
|
// show popover
|
||||||
document.getElementById('popover').style.display = 'none';
|
document.getElementById('popover').style.opacity = '0';
|
||||||
// Activate Carousel
|
// Activate Carousel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,7 +263,7 @@ const handleHashtagDisplayClick = function(serverUrl) {
|
||||||
|
|
||||||
const currentHashtags = getUrlParameter('hashtags').split(',');
|
const currentHashtags = getUrlParameter('hashtags').split(',');
|
||||||
if ( currentHashtags = null ) {
|
if ( currentHashtags = null ) {
|
||||||
currentHasttags = "netzbegruenung,bdk24,wasjetztzählt".split(',')
|
currentHasttags = "netzbegruenung,bdk23".split(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < currentHashtags.length; i++) {
|
for (let i = 0; i < currentHashtags.length; i++) {
|
||||||
|
@ -337,16 +314,11 @@ $(document).ready(async function() {
|
||||||
|
|
||||||
let hashtags = getUrlParameter('hashtags');
|
let hashtags = getUrlParameter('hashtags');
|
||||||
if ( hashtags == '' ) {
|
if ( hashtags == '' ) {
|
||||||
hashtags = "netzbegruenung,bdk24,wasjetztzählt";
|
hashtags = "netzbegruenung,bdk23";
|
||||||
}
|
}
|
||||||
const hashtagsArray = hashtags ? hashtags.split(',') : [];
|
const hashtagsArray = hashtags ? hashtags.split(',') : [];
|
||||||
const serverUrl = getUrlParameter('server') || defaultServerUrl;
|
const serverUrl = getUrlParameter('server') || defaultServerUrl;
|
||||||
|
|
||||||
const enableCarousel = getUrlParameter('nbstand' );
|
|
||||||
if ( enableCarousel == '1' )
|
|
||||||
withCarousel = true;
|
|
||||||
console.log("show carousel: "+withCarousel);
|
|
||||||
|
|
||||||
$('#hashtag-display').on('click', function() {
|
$('#hashtag-display').on('click', function() {
|
||||||
handleHashtagDisplayClick(serverUrl);
|
handleHashtagDisplayClick(serverUrl);
|
||||||
});
|
});
|
||||||
|
@ -362,16 +334,16 @@ $(document).ready(async function() {
|
||||||
$('.masonry-grid').masonry('layout');
|
$('.masonry-grid').masonry('layout');
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
if ( withCarousel) {
|
|
||||||
updateCarousel(slides, allPosts.flat());
|
updateCarousel(slides, allPosts.flat());
|
||||||
setTimeout(async function() { showCarousel(); }, duration);
|
|
||||||
}
|
setTimeout(async function() { showCarousel(); }, duration)
|
||||||
else setTimeout(async function() { hideCarousel(); }, duration);
|
|
||||||
setInterval(async function() {
|
setInterval(async function() {
|
||||||
const newPosts = await Promise.all(hashtagsArray.map(hashtag => fetchPosts(serverUrl, hashtag)));
|
const newPosts = await Promise.all(hashtagsArray.map(hashtag => fetchPosts(serverUrl, hashtag)));
|
||||||
let updated = updateWall(newPosts.flat());
|
let updated = updateWall(newPosts.flat());
|
||||||
if ( withCarousel && updated > 0 ) {
|
if ( updated > 0 ) {
|
||||||
|
hideCarousel()
|
||||||
updateCarousel(slides, newPosts.flat());
|
updateCarousel(slides, newPosts.flat());
|
||||||
|
setTimeout(async function() { showCarousel(); }, duration)
|
||||||
}
|
}
|
||||||
}, refresh);
|
}, refresh);
|
||||||
} else {
|
} 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 {
|
.carousel .card-text {
|
||||||
font-size: 1.6em;
|
font-size: 1.4em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 50vh;
|
max-height: 60vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
|
@ -208,7 +208,7 @@ body {
|
||||||
|
|
||||||
.text-muted {
|
.text-muted {
|
||||||
color:#6c757d !important;
|
color:#6c757d !important;
|
||||||
font-size: 1em;
|
font-size: 1.2em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +224,3 @@ body {
|
||||||
.footer a {
|
.footer a {
|
||||||
color: rgba(255, 255, 255, 0.8) !important;
|
color: rgba(255, 255, 255, 0.8) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji {
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
Loading…
Reference in a new issue