Compare commits

..

29 commits

Author SHA1 Message Date
b23ad379c8
Do not display posts with content warning 2024-11-16 11:19:28 +01:00
6e5d649876 Merge pull request 'Update sharepics' (#8) from max.lissowski/mastowall:pr/new-sharepics into main
Reviewed-on: #8
Reviewed-by: Norbert Tretkowski <norbert@tretkowski.de>
2024-11-15 22:56:17 +01:00
Max
56d57e4255
Update sharepics 2024-11-15 22:52:59 +01:00
02fb2c023f Merge pull request 'script.js aktualisiert' (#7) from paulusr1/mastowall:main into main
Reviewed-on: #7
Reviewed-by: Norbert Tretkowski <norbert@tretkowski.de>
2024-11-15 19:05:09 +01:00
c9953a4e3b script.js aktualisiert
Make sure Carousel does not block links when disabled.
2024-11-15 12:05:15 +01:00
f52d2d2c25 Merge pull request 'main' (#6) from stefan.schmidt-bilkenroth/mastowall:main into main
Reviewed-on: #6
2024-11-15 11:12:01 +01:00
Stefan Schmidt-Bilkenroth
678e08e681 implement nbstand URL argument to enable carousel 2024-11-15 11:00:44 +01:00
Stefan Schmidt-Bilkenroth
973a3b9342 update Readme including nbstand=1 argument 2024-11-15 11:00:13 +01:00
49361092c9
Update Hashtags 2024-11-14 21:07:14 +01:00
6d579f4f45
Update Hashtags 2024-11-14 21:05:11 +01:00
001a361037
Revert "Fix fullscreen mode on iOS/iPadOS"
This reverts commit bd1d3a5e2e.
2024-10-09 06:33:28 +02:00
dbf57bea46
Update hashtag 2024-05-05 09:10:49 +02:00
bd1d3a5e2e
Fix fullscreen mode on iOS/iPadOS 2024-05-04 11:57:17 +02:00
5099cd92a5
Hashtag #wasjetztzählt hinzugefügt 2024-01-21 11:42:57 +01:00
aacb05aee2
Add support for displaying video attachments based on https://github.com/rstockm/mastowall/pull/10 2024-01-20 15:19:31 +01:00
1faf17e67f
Update screenshot in README.md 2024-01-20 13:29:50 +01:00
dcc607df51
Use jpg for screenshot 2024-01-20 13:28:25 +01:00
ba7df6c13e
Add screenshot 2024-01-20 13:27:16 +01:00
1237e84670
Update hashtags 2024-01-20 10:00:38 +01:00
5c111f86d7
Sharepic fuer LDK24 Potsdam 2024-01-20 09:58:54 +01:00
f216b42011 Merge pull request 'Even more improvements' (#5) from lisswoma/mastowall:for-pi2 into main
Reviewed-on: #5
2023-11-25 10:21:11 +01:00
ec902d12f2
Fix extra cards display duration 2023-11-25 02:21:55 +01:00
004c92b392
Add emoji support 2023-11-25 02:18:39 +01:00
7acd5f4e38
Improve long post handling 2023-11-25 01:48:53 +01:00
8efbafef3c
Directly show new posts in carousel 2023-11-25 01:13:05 +01:00
c12f8220f4
Improve font sizes 2023-11-25 01:08:41 +01:00
39138ef889 Merge pull request 'for-pi2' (#4) from for-pi2 into main
Reviewed-on: #4
2023-11-24 21:17:28 +01:00
e821501f6c Link to original project on GitHub 2023-11-24 08:05:29 +01:00
98d8c3b994 Merge pull request 'for-pi2' (#2) from for-pi2 into main
Reviewed-on: #2
2023-11-24 07:54:18 +01:00
11 changed files with 67 additions and 29 deletions

View file

@ -1,9 +1,10 @@
# 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://github.com/rstockm/mastowall/assets/3195116/7060536e-4847-4e38-801e-3c0312b8b194"> <img width="1348" alt="image" src="https://git.verdigado.com/NB-Public/mastowall/raw/branch/main/screenshot.jpg">
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)
@ -37,6 +38,8 @@ 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:
@ -63,7 +66,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` 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`
Enjoy using Mastowall! Enjoy using Mastowall!

View file

@ -6,7 +6,10 @@
"refreshDuration": 30, "refreshDuration": 30,
"maxAge": 604800, "maxAge": 604800,
"extraCards": [ "extraCards": [
"<img src='sharepic.jpg' style='max-width: 100%;max-height: 100%;vertical-align: middle;'>" "<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;'>"
], ],
"includeReplies": true "includeReplies": true
} }

View file

@ -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="bdk23"> <input type="text" class="form-control" id="hashtag2" placeholder="Enter a hashtag" value="bdk24">
</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"> <input type="text" class="form-control" id="hashtag3" placeholder="Enter a hashtag" value="wasjetztzählt">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="serverUrl">Server:</label> <label for="serverUrl">Server:</label>

BIN
screenshot.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

View file

@ -55,6 +55,8 @@ 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() {
@ -77,7 +79,10 @@ const fetchConfig = async function() {
duration = 10000; duration = 10000;
refresh = 30000; refresh = 30000;
extraCards = [ extraCards = [
"<div><img src='sharepic.jpg' style='max-width: 100%;max-height: 100%'></div>" "<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;'>"
]; ];
return "https://gruene.social"; return "https://gruene.social";
} }
@ -104,6 +109,14 @@ 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;
@ -115,10 +128,14 @@ 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">${DOMPurify.sanitize(post.account.display_name)} <span class="user-name">@${DOMPurify.sanitize(post.account.acct)}</span></p> <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>
</div> </div>
${post.media_attachments[0] ? `<img src="${post.media_attachments[0].url}" class="card-img-top mb-2">` : ''} ${post.media_attachments[0] ?
<p class="card-text">${DOMPurify.sanitize(post.content)}</p> (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.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>
@ -133,7 +150,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 return secondsAgo(new Date(post.created_at)) < maxAge && post.content.indexOf("nitter.") === -1 && post.spoiler_text === ""
}); });
return posts; return posts;
@ -185,17 +202,22 @@ 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 = DOMPurify.sanitize(post.content); const postContent = replaceEmojies(DOMPurify.sanitize(post.content), post.emojis);
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">${DOMPurify.sanitize(post.account.display_name)} <span class="user-name">@${DOMPurify.sanitize(post.account.acct)}</span></p> <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>
</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 > 800 ? `style="font-size: 0.9em;"`: ``}>${postContent}</div></div> <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] ? `<div class="col-md-6"><img src="${post.media_attachments[0].url}" class="card-img-bottom" align="center"> </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> </div>
<hr> <hr>
<p class="card-text text-right"> <p class="card-text text-right">
@ -210,7 +232,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-mdb-interval="${duration * 2}" data-mdb-pause="false"> newHTML += `<div class="carousel-item" data-interval="${duration * 2}" data-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]}
@ -225,6 +247,7 @@ 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");
} }
@ -235,8 +258,8 @@ const strip = function(html) {
} }
const hideCarousel = function() { const hideCarousel = function() {
// show popover // hide popover
document.getElementById('popover').style.opacity = '0'; document.getElementById('popover').style.display = 'none';
// Activate Carousel // Activate Carousel
} }
@ -263,7 +286,7 @@ const handleHashtagDisplayClick = function(serverUrl) {
const currentHashtags = getUrlParameter('hashtags').split(','); const currentHashtags = getUrlParameter('hashtags').split(',');
if ( currentHashtags = null ) { if ( currentHashtags = null ) {
currentHasttags = "netzbegruenung,bdk23".split(',') currentHasttags = "netzbegruenung,bdk24,wasjetztzählt".split(',')
} }
for (let i = 0; i < currentHashtags.length; i++) { for (let i = 0; i < currentHashtags.length; i++) {
@ -314,11 +337,16 @@ $(document).ready(async function() {
let hashtags = getUrlParameter('hashtags'); let hashtags = getUrlParameter('hashtags');
if ( hashtags == '' ) { if ( hashtags == '' ) {
hashtags = "netzbegruenung,bdk23"; hashtags = "netzbegruenung,bdk24,wasjetztzählt";
} }
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);
}); });
@ -334,16 +362,16 @@ $(document).ready(async function() {
$('.masonry-grid').masonry('layout'); $('.masonry-grid').masonry('layout');
}, 2000); }, 2000);
updateCarousel(slides, allPosts.flat()); if ( withCarousel) {
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 ( updated > 0 ) { if ( withCarousel && updated > 0 ) {
hideCarousel()
updateCarousel(slides, newPosts.flat()); updateCarousel(slides, newPosts.flat());
setTimeout(async function() { showCarousel(); }, duration)
} }
}, refresh); }, refresh);
} else { } else {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

BIN
sharepics/Slide1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
sharepics/Slide2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
sharepics/Slide3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
sharepics/Slide4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View file

@ -51,9 +51,9 @@
} }
.carousel .card-text { .carousel .card-text {
font-size: 1.4em; font-size: 1.6em;
overflow: hidden; overflow: hidden;
max-height: 60vh; max-height: 50vh;
} }
.card { .card {
@ -208,7 +208,7 @@ body {
.text-muted { .text-muted {
color:#6c757d !important; color:#6c757d !important;
font-size: 1.2em; font-size: 1em;
text-align: right; text-align: right;
} }
@ -223,4 +223,8 @@ body {
.footer a { .footer a {
color: rgba(255, 255, 255, 0.8) !important; color: rgba(255, 255, 255, 0.8) !important;
}
.emoji {
height: 1em;
} }