Compare commits
81 commits
Author | SHA1 | Date | |
---|---|---|---|
b23ad379c8 | |||
6e5d649876 | |||
56d57e4255 | |||
02fb2c023f | |||
c9953a4e3b | |||
f52d2d2c25 | |||
![]() |
678e08e681 | ||
![]() |
973a3b9342 | ||
49361092c9 | |||
6d579f4f45 | |||
001a361037 | |||
dbf57bea46 | |||
bd1d3a5e2e | |||
5099cd92a5 | |||
aacb05aee2 | |||
1faf17e67f | |||
dcc607df51 | |||
ba7df6c13e | |||
1237e84670 | |||
5c111f86d7 | |||
f216b42011 | |||
ec902d12f2 | |||
004c92b392 | |||
7acd5f4e38 | |||
8efbafef3c | |||
c12f8220f4 | |||
39138ef889 | |||
945e09b3ac | |||
c274648f09 | |||
876f2bb7ad | |||
6f9459fb3e | |||
b0051867aa | |||
12fca72480 | |||
e821501f6c | |||
98d8c3b994 | |||
bcaa5a5edd | |||
3ba54e1653 | |||
c76dfedac1 | |||
de46f7c7b8 | |||
8cdc4ea5c3 | |||
191dc33e28 | |||
9cab9e8b1c | |||
df73c736dd | |||
3ffdeabf33 | |||
395ce728bf | |||
75d4e84e5c | |||
eb85222ea3 | |||
7edce5ab0f | |||
038546df49 | |||
65d9454666 | |||
ec6c88d83e | |||
53f5b71b32 | |||
5b2d634bf0 | |||
06080e09a1 | |||
c951f47034 | |||
7969da352a | |||
1f6d85a072 | |||
515aa7f86c | |||
752f06f68f | |||
63d5eafc01 | |||
4c527ec4c4 | |||
a4891e24bb | |||
65438d8799 | |||
42f41cc360 | |||
73163da9ec | |||
3f28ac8ad2 | |||
9d07ff6a63 | |||
32068def92 | |||
8dde3b2827 | |||
![]() |
a043940301 | ||
![]() |
f0b2e205d9 | ||
![]() |
34ff6c48bd | ||
![]() |
abdd8a83de | ||
![]() |
b2f7055164 | ||
![]() |
a9c4621eff | ||
![]() |
9f82492a96 | ||
![]() |
321bb89b33 | ||
![]() |
36eb5bc1db | ||
![]() |
afb2eb06e8 | ||
![]() |
1c28ce654d | ||
![]() |
ed73ee845a |
15 changed files with 11616 additions and 48 deletions
12
README.md
12
README.md
|
@ -1,14 +1,16 @@
|
||||||
# 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="1435" alt="image" src="https://github.com/rstockm/mastowall/assets/3195116/af1a3786-335b-4d64-b387-0db75dd73040">
|
<img width="1348" alt="image" src="https://git.verdigado.com/NB-Public/mastowall/raw/branch/main/screenshot.jpg">
|
||||||
|
|
||||||
Try it live: [Mastowall for the BiblioCon conference](https://rstockm.github.io/mastowall/?hashtags=111bibliocon,bibliocon,bibliocon23&server=https://openbiblio.social))
|
Try it live: [Mastowall for BDK23](https://tretkowski.de/mastowall/?hashtags=bdk23,netzbegruenung&server=https://gruene.social)
|
||||||
|
|
||||||
Use your own hashtags and server:
|
Use your own hashtags and server:
|
||||||
|
|
||||||
<img width="1164" alt="image" src="https://github.com/rstockm/mastowall/assets/3195116/97814e5b-5377-426a-bfff-78be8f560421">
|
<img width="1108" alt="image" src="https://github.com/rstockm/mastowall/assets/3195116/761237da-2166-46c5-8f31-46b9e913f736">
|
||||||
|
|
||||||
JSON config file:
|
JSON config file:
|
||||||
|
|
||||||
|
@ -36,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:
|
||||||
|
@ -62,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!
|
||||||
|
|
||||||
|
|
16
README_SSB.md
Normal file
16
README_SSB.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
## Additions made by SSB for BDK23
|
||||||
|
|
||||||
|
# added carousel for posts
|
||||||
|
|
||||||
|
## Important
|
||||||
|
|
||||||
|
install color emojis:
|
||||||
|
sudo apt-get install fonts-noto-color-emoji
|
||||||
|
|
||||||
|
install xdotool to move the mouse to bottom right (does not work with Bookworm/Wayland)
|
||||||
|
sudo apt-get install xdotool -y
|
||||||
|
|
||||||
|
run in kiosk mode:
|
||||||
|
chromium-browser --kiosk index.html
|
||||||
|
|
||||||
|
or use the remote URL instead of index.html
|
10159
bootstrap.min.css
vendored
Normal file
10159
bootstrap.min.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
15
config.json
15
config.json
|
@ -1,6 +1,15 @@
|
||||||
{
|
{
|
||||||
"navbarBrandText": "Mastowall 1.1 - written by ChatGPT4 - Prompting: Ralf Stockmann (rstockm)",
|
"navbarBrandText": "Netzbegrünung Mastowall - gruene.social",
|
||||||
"defaultServerUrl": "https://mastodon.social",
|
"defaultServerUrl": "https://gruene.social",
|
||||||
"navbarColor": "#333355",
|
"navbarColor": "#008939",
|
||||||
|
"duration": 10,
|
||||||
|
"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;'>"
|
||||||
|
],
|
||||||
"includeReplies": true
|
"includeReplies": true
|
||||||
}
|
}
|
||||||
|
|
38
index.html
38
index.html
|
@ -3,9 +3,21 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Mastowall 1.1</title>
|
<title>Netzbegrünung Mastowall</title>
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<link rel="apple-touch-icon" href="mastowall-favicon.png">
|
||||||
|
<link rel="icon" href="mastowall-favicon.png" type="image/x-icon">
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.imagesloaded/4.1.4/imagesloaded.pkgd.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.3/purify.min.js"></script>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-light">
|
<nav class="navbar navbar-light">
|
||||||
|
@ -22,19 +34,19 @@
|
||||||
<form id="hashtag-form">
|
<form id="hashtag-form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="hashtag1">Hashtag 1:</label>
|
<label for="hashtag1">Hashtag 1:</label>
|
||||||
<input type="text" class="form-control" id="hashtag1" placeholder="Enter a hashtag">
|
<input type="text" class="form-control" id="hashtag1" placeholder="Enter a hashtag" value="netzbegruenung">
|
||||||
</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">
|
<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>
|
||||||
<input type="text" class="form-control" id="serverUrl" placeholder="Enter server URL" value="https://mastodon.social">
|
<input type="text" class="form-control" id="serverUrl" placeholder="Enter server URL" value="https://gruene.social">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Reload</button>
|
<button type="submit" class="btn btn-primary">Reload</button>
|
||||||
|
@ -48,17 +60,17 @@
|
||||||
<div class="row masonry-grid" id="wall"></div>
|
<div class="row masonry-grid" id="wall"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="popover" class="popover">
|
||||||
|
<div id="myCarousel" class="carousel slide" data-mdb-ride="carousel" data-mdb-pause="false">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<footer class="footer text-center py-4 mt-5">
|
<footer class="footer text-center py-4 mt-5">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span class="text-muted">Host your own Mastowall - check </span>
|
<a href="https://netzbegruenung.de/" target="_blank">Netzbegrünung</a>
|
||||||
<a href="https://github.com/rstockm/mastowall" target="_blank">GitHub</a>
|
<span class="text-muted"> - Verein für GRÜNE Netzkultur e.V.</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
|
|
||||||
<script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.imagesloaded/4.1.4/imagesloaded.pkgd.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.3/purify.min.js"></script>
|
|
||||||
<script src="script.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
1069
masonry.pkgd.min.js
vendored
Normal file
1069
masonry.pkgd.min.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
BIN
mastowall-favicon.png
Normal file
BIN
mastowall-favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
6
mastowall.desktop
Normal file
6
mastowall.desktop
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=MastoWall Autostart
|
||||||
|
Comment=Starten der MastoWall zur BDK23
|
||||||
|
NoDisplay=false
|
||||||
|
Exec=sh -c 'cd /home/ssb/Documents/mastowall && xdotool mousemove 1920 1080 && sleep 5 && chromium-browser --kiosk index.html'
|
BIN
screenshot.jpg
Normal file
BIN
screenshot.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 410 KiB |
250
script.js
250
script.js
|
@ -6,6 +6,7 @@ function getUrlParameter(name) {
|
||||||
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
||||||
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
||||||
var results = regex.exec(location.search);
|
var results = regex.exec(location.search);
|
||||||
|
console.log(results)
|
||||||
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,23 +17,46 @@ const secondsAgo = date => Math.floor((new Date() - date) / 1000);
|
||||||
const timeAgo = function(seconds) {
|
const timeAgo = function(seconds) {
|
||||||
// An array of intervals for years, months, days, hours, and minutes.
|
// An array of intervals for years, months, days, hours, and minutes.
|
||||||
const intervals = [
|
const intervals = [
|
||||||
{ limit: 31536000, text: 'years' },
|
{ limit: 31536000, singular: 'Jahre', plural: 'Jahren' },
|
||||||
{ limit: 2592000, text: 'months' },
|
{ limit: 2592000, singular: 'Monat', plural: 'Monaten' },
|
||||||
{ limit: 86400, text: 'days' },
|
{ limit: 86400, singular: 'Tag', plural: 'Tagen' },
|
||||||
{ limit: 3600, text: 'hours' },
|
{ limit: 3600, singular: 'Stunde', plural: 'Stunden' },
|
||||||
{ limit: 60, text: 'minutes' }
|
{ limit: 60, singular: 'Minute', plural: 'Minuten' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// Loop through the intervals to find which one is the best fit.
|
// Loop through the intervals to find which one is the best fit.
|
||||||
for (let interval of intervals) {
|
for (let interval of intervals) {
|
||||||
if (seconds >= interval.limit) {
|
if (seconds >= interval.limit) {
|
||||||
return Math.floor(seconds / interval.limit) + ` ${interval.text} ago`;
|
let amount = Math.floor(seconds / interval.limit);
|
||||||
|
let text;
|
||||||
|
if (amount !== 1) {
|
||||||
|
text = interval.plural;
|
||||||
|
} else {
|
||||||
|
text = interval.singular;
|
||||||
|
}
|
||||||
|
return `Vor ${amount} ${text}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Math.floor(seconds) + " seconds ago";
|
let text = "Sekunde";
|
||||||
|
let amount = Math.floor(seconds);
|
||||||
|
if (amount !== 1) {
|
||||||
|
text += "n";
|
||||||
|
}
|
||||||
|
return `Vor ${amount} ${text}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
let includeReplies;
|
let includeReplies;
|
||||||
|
// max post age in seconds
|
||||||
|
let maxAge;
|
||||||
|
// below times are in milliseconds
|
||||||
|
// duration for slide animations
|
||||||
|
let duration;
|
||||||
|
// refresh rate
|
||||||
|
let refresh;
|
||||||
|
// extra cards text
|
||||||
|
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() {
|
||||||
|
@ -41,9 +65,26 @@ const fetchConfig = async function() {
|
||||||
$('#navbar-brand').text(config.navbarBrandText);
|
$('#navbar-brand').text(config.navbarBrandText);
|
||||||
$('.navbar').css('background-color', config.navbarColor);
|
$('.navbar').css('background-color', config.navbarColor);
|
||||||
includeReplies = config.includeReplies;
|
includeReplies = config.includeReplies;
|
||||||
|
maxAge = config.maxAge;
|
||||||
|
duration = config.duration * 1000;
|
||||||
|
refresh = config.refreshDuration * 1000;
|
||||||
|
extraCards = config.extraCards;
|
||||||
return config.defaultServerUrl;
|
return config.defaultServerUrl;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading config.json:", error);
|
console.log("Error loading config.json:", error);
|
||||||
|
$('#navbar-brand').text("Netzbegrünung Mastowall - gruene.social");
|
||||||
|
$('.navbar').css('background-color', "#008939");
|
||||||
|
includeReplies = true;
|
||||||
|
maxAge = 60 * 60 * 24 * 7;
|
||||||
|
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;'>"
|
||||||
|
];
|
||||||
|
return "https://gruene.social";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,16 +100,26 @@ const fetchPosts = async function(serverUrl, hashtag) {
|
||||||
|
|
||||||
// updateTimesOnPage updates the time information displayed for each post
|
// updateTimesOnPage updates the time information displayed for each post
|
||||||
const updateTimesOnPage = function() {
|
const updateTimesOnPage = function() {
|
||||||
$('.card-text a').each(function() {
|
$('.card-text a.time').each(function() {
|
||||||
const date = new Date($(this).attr('data-time'));
|
const timeValue = $(this).attr('data-time');
|
||||||
|
if (timeValue === '') return;
|
||||||
|
const date = new Date(timeValue);
|
||||||
const newTimeAgo = timeAgo(secondsAgo(date));
|
const newTimeAgo = timeAgo(secondsAgo(date));
|
||||||
$(this).text(newTimeAgo);
|
$(this).text(newTimeAgo);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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;
|
if (existingPosts.includes(post.id) || (!includeReplies && post.in_reply_to_id !== null)) return 0;
|
||||||
|
|
||||||
existingPosts.push(post.id);
|
existingPosts.push(post.id);
|
||||||
|
|
||||||
|
@ -77,12 +128,16 @@ 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)}</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 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>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -90,27 +145,149 @@ const displayPost = function(post) {
|
||||||
let $card = $(cardHTML);
|
let $card = $(cardHTML);
|
||||||
$('#wall').prepend($card);
|
$('#wall').prepend($card);
|
||||||
$('.masonry-grid').masonry('prepended', $card);
|
$('.masonry-grid').masonry('prepended', $card);
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 posts;
|
||||||
};
|
};
|
||||||
|
|
||||||
// updateWall displays all posts
|
// updateWall displays all posts
|
||||||
const updateWall = function(posts) {
|
const updateWall = function(posts) {
|
||||||
if (!posts || posts.length === 0) return;
|
if (!posts || posts.length === 0) return;
|
||||||
|
|
||||||
|
posts = processPosts(posts);
|
||||||
|
|
||||||
posts.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
|
posts.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
|
||||||
posts.forEach(post => displayPost(post));
|
|
||||||
|
let ret = 0
|
||||||
|
posts.forEach(post => ret += displayPost(post));
|
||||||
|
$('.masonry-grid').masonry('layout');
|
||||||
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// updateCarousel
|
||||||
|
const updateCarousel = function(slides, posts) {
|
||||||
|
if (!posts || posts.length === 0) return;
|
||||||
|
|
||||||
|
posts = processPosts(posts);
|
||||||
|
|
||||||
|
posts.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||||
|
|
||||||
|
// remove slides in carousel
|
||||||
|
slides.innerHTML = "";
|
||||||
|
var newHTML = ` <!-- No Indicators -->`
|
||||||
|
newHTML += `<!-- the slides -->
|
||||||
|
<div class="carousel-inner">
|
||||||
|
`;
|
||||||
|
|
||||||
|
let existingCards = [];
|
||||||
|
|
||||||
|
for( let i = 0; i < posts.length; i++ ) {
|
||||||
|
let post = posts[i];
|
||||||
|
|
||||||
|
if (existingCards.includes(post.id) || (!includeReplies && post.in_reply_to_id !== null)) continue;
|
||||||
|
|
||||||
|
existingCards.push(post.id);
|
||||||
|
|
||||||
|
/*console.log( post.content )*/
|
||||||
|
if ( i == 0 ) {
|
||||||
|
newHTML += `<div class="carousel-item active" data-interval="${duration}" data-pause="false">`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newHTML += `<div class="carousel-item" data-interval="${duration}" data-pause="false">`;
|
||||||
|
}
|
||||||
|
const postContent = replaceEmojies(DOMPurify.sanitize(post.content), post.emojis);
|
||||||
|
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>
|
||||||
|
</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>
|
||||||
|
<hr>
|
||||||
|
<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>
|
||||||
|
${post.favourites_count ? `, <b>${post.favourites_count}</b> mal favorisiert` : '' }
|
||||||
|
${post.replies_count ? `, <b>${post.replies_count}</b> mal kommentiert` : '' }
|
||||||
|
${post.reblogs_count ? `, <b>${post.reblogs_count}</b> mal geteilt` : '' }
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
newHTML += '</div>';
|
||||||
|
}
|
||||||
|
for( let i = 0; i < extraCards.length; i++ ) {
|
||||||
|
newHTML += `<div class="carousel-item" data-interval="${duration * 2}" data-pause="false">
|
||||||
|
<div className="card-big">
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
${extraCards[i]}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
newHTML += '</div>'
|
||||||
|
document.getElementById("myCarousel").innerHTML = newHTML;
|
||||||
|
};
|
||||||
|
|
||||||
|
const showCarousel = function() {
|
||||||
|
// show popover
|
||||||
|
document.getElementById('popover').style.opacity = '1';
|
||||||
|
document.getElementById('popover').style.display = 'block';
|
||||||
|
// Activate Carousel
|
||||||
|
$('#myCarousel').carousel("cycle");
|
||||||
|
}
|
||||||
|
|
||||||
|
const strip = function(html) {
|
||||||
|
let doc = new DOMParser().parseFromString(html, 'text/html');
|
||||||
|
return doc.body.textContent || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideCarousel = function() {
|
||||||
|
// hide popover
|
||||||
|
document.getElementById('popover').style.display = 'none';
|
||||||
|
// Activate Carousel
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashtagsString returns a single string based on the given array of hashtags
|
||||||
|
const hashtagsString = function(hashtagsArray) {
|
||||||
|
return `${hashtagsArray.map(hashtag => `#${hashtag}`).join(' ')}`;
|
||||||
|
}
|
||||||
|
|
||||||
// updateHashtagsOnPage updates the displayed hashtags
|
// updateHashtagsOnPage updates the displayed hashtags
|
||||||
const updateHashtagsOnPage = function(hashtagsArray) {
|
const updateHashtagsOnPage = function(hashtagsArray) {
|
||||||
$('#hashtag-display').text(hashtagsArray.length > 0 ? `${hashtagsArray.map(hashtag => `#${hashtag}`).join(' ')}` : 'No hashtags set');
|
$('#hashtag-display').text(hashtagsArray.length > 0 ? hashtagsString(hashtagsArray) : 'No hashtags set');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// updateHashtagsOnPage updates the document title by appending the given array of hashtags
|
||||||
|
const updateHashtagsInTitle = function(hashtagsArray) {
|
||||||
|
const baseTitle = document.title;
|
||||||
|
document.title = `${baseTitle} | ${hashtagsString(hashtagsArray)}`;
|
||||||
|
}
|
||||||
|
|
||||||
// handleHashtagDisplayClick handles the event when the hashtag display is clicked
|
// handleHashtagDisplayClick handles the event when the hashtag display is clicked
|
||||||
const handleHashtagDisplayClick = function(serverUrl) {
|
const handleHashtagDisplayClick = function(serverUrl) {
|
||||||
$('#app-content').addClass('d-none');
|
$('#app-content').addClass('d-none');
|
||||||
$('#zero-state').removeClass('d-none');
|
$('#zero-state').removeClass('d-none');
|
||||||
|
|
||||||
const currentHashtags = getUrlParameter('hashtags').split(',');
|
const currentHashtags = getUrlParameter('hashtags').split(',');
|
||||||
|
if ( currentHashtags = null ) {
|
||||||
|
currentHasttags = "netzbegruenung,bdk24,wasjetztzählt".split(',')
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < currentHashtags.length; i++) {
|
for (let i = 0; i < currentHashtags.length; i++) {
|
||||||
$(`#hashtag${i+1}`).val(currentHashtags[i]);
|
$(`#hashtag${i+1}`).val(currentHashtags[i]);
|
||||||
|
@ -156,34 +333,61 @@ $(document).ready(async function() {
|
||||||
|
|
||||||
setInterval(function() {
|
setInterval(function() {
|
||||||
$('.masonry-grid').masonry('layout');
|
$('.masonry-grid').masonry('layout');
|
||||||
}, 10000);
|
}, refresh);
|
||||||
|
|
||||||
const hashtags = getUrlParameter('hashtags');
|
let hashtags = getUrlParameter('hashtags');
|
||||||
|
if ( hashtags == '' ) {
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const slides = $('#myCarousel');
|
||||||
|
const popover = $('#popover');
|
||||||
|
|
||||||
if (hashtagsArray.length > 0 && hashtagsArray[0] !== '') {
|
if (hashtagsArray.length > 0 && hashtagsArray[0] !== '') {
|
||||||
const allPosts = await Promise.all(hashtagsArray.map(hashtag => fetchPosts(serverUrl, hashtag)));
|
let allPosts = await Promise.all(hashtagsArray.map(hashtag => fetchPosts(serverUrl, hashtag)));
|
||||||
|
|
||||||
updateWall(allPosts.flat());
|
updateWall(allPosts.flat());
|
||||||
|
setTimeout(function() {
|
||||||
|
$('.masonry-grid').masonry('layout');
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
if ( withCarousel) {
|
||||||
|
updateCarousel(slides, allPosts.flat());
|
||||||
|
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)));
|
||||||
updateWall(newPosts.flat());
|
let updated = updateWall(newPosts.flat());
|
||||||
}, 10000);
|
if ( withCarousel && updated > 0 ) {
|
||||||
|
updateCarousel(slides, newPosts.flat());
|
||||||
|
}
|
||||||
|
}, refresh);
|
||||||
} else {
|
} else {
|
||||||
$('#zero-state').removeClass('d-none');
|
$('#zero-state').removeClass('d-none');
|
||||||
|
$('#popover').removeClass('popover');
|
||||||
$('#app-content').addClass('d-none');
|
$('#app-content').addClass('d-none');
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHashtagsOnPage(hashtagsArray);
|
updateHashtagsOnPage(hashtagsArray);
|
||||||
|
updateHashtagsInTitle(hashtagsArray);
|
||||||
|
|
||||||
$('#hashtag-form').on('submit', function(e) {
|
$('#hashtag-form').on('submit', function(e) {
|
||||||
handleHashtagFormSubmit(e, hashtagsArray);
|
handleHashtagFormSubmit(e, hashtagsArray);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
updateTimesOnPage();
|
updateTimesOnPage();
|
||||||
setInterval(updateTimesOnPage, 60000);
|
setInterval(updateTimesOnPage, 60000);
|
||||||
});
|
});
|
||||||
|
|
BIN
sharepics/Slide1.png
Normal file
BIN
sharepics/Slide1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
BIN
sharepics/Slide2.png
Normal file
BIN
sharepics/Slide2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
BIN
sharepics/Slide3.png
Normal file
BIN
sharepics/Slide3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 101 KiB |
BIN
sharepics/Slide4.png
Normal file
BIN
sharepics/Slide4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
99
styles.css
99
styles.css
|
@ -1,8 +1,12 @@
|
||||||
|
@charset "utf-8";
|
||||||
|
|
||||||
/* Add some custom CSS for the cards */
|
/* Add some custom CSS for the cards */
|
||||||
.card {
|
.card {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.2);
|
box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
background-color: rgb(240,255,240);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Position the avatar and username in the top left of the card */
|
/* Position the avatar and username in the top left of the card */
|
||||||
|
@ -46,6 +50,12 @@
|
||||||
margin-bottom: 1px !important;
|
margin-bottom: 1px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.carousel .card-text {
|
||||||
|
font-size: 1.6em;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
font-size: 0.9em; /* adjust this value to get the desired text size */
|
font-size: 0.9em; /* adjust this value to get the desired text size */
|
||||||
}
|
}
|
||||||
|
@ -62,8 +72,8 @@
|
||||||
|
|
||||||
/* Custom navbar styles */
|
/* Custom navbar styles */
|
||||||
.navbar {
|
.navbar {
|
||||||
height: 50px; /* reduce the height of the navbar */
|
min-height: 50px; /* reduce the height of the navbar */
|
||||||
background-color: rgb(227, 6, 19);
|
background-color: rgb(0, 137, 57);
|
||||||
margin-bottom: 10px !important;
|
margin-bottom: 10px !important;
|
||||||
top: -10px !important;
|
top: -10px !important;
|
||||||
padding-top: 14px !important;
|
padding-top: 14px !important;
|
||||||
|
@ -72,13 +82,13 @@
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
color: rgba(255, 255, 255, 0.8) !important; /* change the text color */
|
color: rgba(255, 255, 255, 0.8) !important; /* change the text color */
|
||||||
margin: 0 auto; /* center the brand name */
|
margin: 0 auto; /* center the brand name */
|
||||||
font-size: 0.9em;
|
font-size: 1.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-info {
|
.navbar-info {
|
||||||
color: rgba(255, 255, 255, 0.8) !important; /* change the text color */
|
color: rgba(255, 255, 255, 0.8) !important; /* change the text color */
|
||||||
margin: 0 auto; /* center the brand name */
|
margin: 0 auto; /* center the brand name */
|
||||||
font-size: 1.2em;
|
font-size: 2.4em;
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,12 +134,28 @@ body {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar-img-big {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-name {
|
||||||
|
font-size:2em;
|
||||||
|
font-weight:600;
|
||||||
|
padding-top:0.3em
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-weight: normal;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 2000px !important;
|
max-width: 2000px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
background-color: rgb(200, 200, 200);
|
background-color: rgb(0, 137, 57);
|
||||||
color: #f2f2f2;
|
color: #f2f2f2;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -139,3 +165,66 @@ body {
|
||||||
padding-bottom: 2px !important; /* reduce padding-bottom to half */
|
padding-bottom: 2px !important; /* reduce padding-bottom to half */
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popover {
|
||||||
|
position: fixed;
|
||||||
|
top: 10%;
|
||||||
|
left: 10%;
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
max-height: 80%;
|
||||||
|
max-width: 80%;
|
||||||
|
background-color: rgba(255, 255, 255, .95);
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 0px solid rgba(0, 137, 57, .5);
|
||||||
|
box-shadow:0 .5rem 1rem rgba(0, 0, 0, .30); !important
|
||||||
|
border-radius:.8rem;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.5s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-big {
|
||||||
|
font-size: 1.8em;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-top: 20px !important;
|
||||||
|
margin-left: 40px !important;
|
||||||
|
margin-right: 40px !important;
|
||||||
|
margin-bottom: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-img-bottom {
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: 500px;
|
||||||
|
width: auto;
|
||||||
|
text-align: center;
|
||||||
|
aspect-ratio: auto !important;
|
||||||
|
border-top-left-radius: calc(.25rem - 1px);
|
||||||
|
border-top-right-radius:calc(.25rem - 1px);
|
||||||
|
margin-top: 0px !important;
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
align-items: center;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color:#6c757d !important;
|
||||||
|
font-size: 1em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer .text-muted {
|
||||||
|
color: rgba(255, 255, 255, 0.8) !important;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
color: rgba(255, 255, 255, 0.8) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
height: 1em;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue