Initial commit

This commit is contained in:
moanos [he/him] 2023-11-26 22:00:42 +01:00
commit da407c0772
6 changed files with 555 additions and 0 deletions

14
.woodpecker.yml Normal file
View File

@ -0,0 +1,14 @@
---
deploy:
image: appleboy/drone-scp
settings:
strip_components: 1
host:
from_secret: host
username:
from_secret: ssh_user
target:
from_secret: path
source: src/
key:
from_secret: ssh_key

9
README.md Normal file
View File

@ -0,0 +1,9 @@
# Include Fedi
Include Fedi allows you to embed a fediverse accounts post into a sites
```html
<div class="include-mastodon-feed dark" id="fedi-feed">Loading...</div>
```
[include-mastodon-feed instance="social.queereszentrumtuebingen.de" account="01JFERHG3QG06PQ7QGYEZ6288P" limit=3 excludeReplies=true excludeBoosts=true text-permalinkPre=am date-locale=de-DE]

16
src/LICENSE.txt Normal file
View File

@ -0,0 +1,16 @@
_____ _ _ _
| ____|_ ___ __ __ _| |_ | | (_) ___ ___ _ __ ___ ___
| _| \ \/ / '_ \ / _` | __| | | | |/ __/ _ \ '_ \/ __|/ _ \
| |___ > <| |_) | (_| | |_ | |___| | (_| __/ | | \__ \ __/
|_____/_/\_\ .__/ \__,_|\__| |_____|_|\___\___|_| |_|___/\___|
|_|
https://directory.fsf.org/wiki/License:Expat
Copyright 2022 wolfgang.lol, moanos
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

14
src/index.html Normal file
View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class=""
lang="de-DE">
<head>
<script src="script.js"></script>
<link rel='stylesheet' id='include-fedi-css' href='style.css'/>
</head>
<body>
<div class="include-mastodon-feed dark" id="fedi-feed">Loading...</div>
</body>
</html>

328
src/script.js Normal file
View File

@ -0,0 +1,328 @@
const mastodonFeedCreateElement = function(type, className = null) {
let element = document.createElement(type);
if(null !== className) {
element.className = className;
}
return element;
}
const mastodonFeedCreateElementAccountLink = function(account) {
let accountLinkElem = mastodonFeedCreateElement('a');
accountLinkElem.href = account.url;
let accountImageElem = mastodonFeedCreateElement('img', 'avatar');
accountImageElem.src = account.avatar_static;
accountLinkElem.addEventListener('mouseover', (event) => {
accountLinkElem.querySelector('.avatar').src = account.avatar;
});
accountLinkElem.addEventListener('mouseout', (event) => {
accountLinkElem.querySelector('.avatar').src = account.avatar_static;
});
accountLinkElem.appendChild(accountImageElem);
// inject emojis
let displayName = account.display_name;
if(account.emojis.length > 0) {
account.emojis.forEach(function(emoji) {
displayName = mastodonFeedInjectEmoji(displayName, emoji);
});
}
accountLinkElem.innerHTML += ' ' + displayName;
return accountLinkElem;
}
const mastodonFeedCreateElementPermalink = function(status, label) {
let linkElem = mastodonFeedCreateElement('a');
linkElem.href = status.url;
linkElem.appendChild(document.createTextNode(label));
return linkElem;
}
const mastodonFeedCreateElementMediaAttachments = function(status, options) {
let attachments = status.media_attachments;
let mediaWrapperElem = mastodonFeedCreateElement('div', 'media');
for(let mediaIndex = 0; mediaIndex < attachments.length; mediaIndex++) {
let media = attachments[mediaIndex];
let mediaElem = mastodonFeedCreateElement('div', media.type);
if('image' == media.type) {
let mediaElemImgLink = mastodonFeedCreateElement('a');
mediaElemImgLink.href = status.url;
if(null === media.remote_url) {
mediaElemImgLink.style.backgroundImage = 'url("' + media.preview_url + '")';
}
else {
mediaElemImgLink.style.backgroundImage = 'url("' + media.remote_url + '")';
}
if(null !== media.description) {
mediaElem.title = media.description;
}
mediaElem.appendChild(mediaElemImgLink);
}
else if('gifv' == media.type) {
let mediaElemGifvLink = mastodonFeedCreateElement('a');
mediaElemGifvLink.href = status.url;
let mediaElemGifv = mastodonFeedCreateElement('video', 'requiresInteraction');
if(null === media.remote_url) {
mediaElemGifv.src = media.url;
}
else {
mediaElemGifv.src = media.remote_url;
}
mediaElemGifv.loop = true;
mediaElemGifv.muted = 'muted';
if(null !== media.description) {
mediaElemGifv.title = media.description;
}
mediaElemGifvLink.appendChild(mediaElemGifv);
mediaElem.appendChild(mediaElemGifvLink);
mediaElemGifv.addEventListener('mouseover', (event) => {
mediaElemGifv.play();
});
mediaElemGifv.addEventListener('mouseout', (event) => {
mediaElemGifv.pause();
mediaElemGifv.currentTime = 0;
});
}
else {
// TODO implement support for other media types
// currently only image and gifv support implemented
mediaElem.innerHTML = 'Stripped ' + media.type + ' - only available on instance<br />';
let permalinkElem = mastodonFeedCreateElement('span', 'permalink');
permalinkElem.appendChild(mastodonFeedCreateElementPermalink(status, options.text.viewOnInstance));
mediaElem.appendChild(permalinkElem);
}
mediaWrapperElem.appendChild(mediaElem);
}
return mediaWrapperElem;
}
const mastodonFeedCreateElementPreviewCard = function(card) {
let cardElem = mastodonFeedCreateElement('div', 'card');
if(null === card.html || card.html.length < 1) {
let cardElemMeta = mastodonFeedCreateElement('div', 'meta');
if(null !== card.image) {
let cardElemImageWrapper = mastodonFeedCreateElement('div', 'image');
let cardElemImage = mastodonFeedCreateElement('img');
cardElemImage.src = card.image;
cardElemImageWrapper.appendChild(cardElemImage);
cardElemMeta.appendChild(cardElemImageWrapper);
}
let cardElemTitle = mastodonFeedCreateElement('div', 'title');
cardElemTitle.innerHTML = card.title;
cardElemMeta.appendChild(cardElemTitle);
let cardElemDescription = mastodonFeedCreateElement('div', 'description');
cardElemDescription.innerHTML = card.description;
cardElemMeta.appendChild(cardElemDescription);
if(card.url === null) {
cardElem.appendChild(cardElemMeta);
}
else {
let cardElemLink = mastodonFeedCreateElement('a');
cardElemLink.href = card.url;
cardElemLink.appendChild(cardElemMeta);
cardElem.appendChild(cardElemLink);
}
}
else {
cardElem.innerHTML = card.html;
}
return cardElem;
}
const mastodonFeedCreateElementTimeinfo = function(status, options, url = false) {
let createdInfo = mastodonFeedCreateElement('span', 'permalink');
createdInfo.innerHTML = ' ' + options.text.permalinkPre + ' ';
if(false === url) {
createdInfo.innerHTML += new Date(status.created_at).toLocaleString(options.localization.date.locale, options.localization.date.options);
}
else {
createdInfo.appendChild(mastodonFeedCreateElementPermalink(status, new Date(status.created_at).toLocaleString(options.localization.date.locale, options.localization.date.options)));
}
createdInfo.innerHTML += ' ' + options.text.permalinkPost + ' ';
if(null !== status.edited_at) {
createdInfo.innerHTML += ' ' + options.text.edited;
}
return createdInfo;
}
const mastodonFeedInjectEmoji = function(string, emoji) {
return string.replace(':' + emoji.shortcode + ':', '<img class="emoji" src="' + emoji.url + '" title="' + emoji.shortcode + '" />');
}
const mastodonFeedRenderStatuses = function(statuses, rootElem, options) {
if(statuses.length < 1) {
console.log(options);
rootElem.innerHTML = options.text.noStatuses;
}
else {
for(let i = 0; i < statuses.length; i++) {
let status = statuses[i];
let isEdited = (null === status.edited_at ? true : false);
let isReblog = (null === status.reblog ? false : true);
let statusElem = mastodonFeedCreateElement('div', 'status');
// add account meta info
let accountElem = mastodonFeedCreateElement('div', 'account');
if(isReblog) {
let boosterElem = mastodonFeedCreateElement('span', 'booster');
boosterElem.appendChild(document.createTextNode( options.text.boosted ));
accountElem.appendChild(boosterElem);
}
accountElem.appendChild(mastodonFeedCreateElementAccountLink(status.account));
accountElem.appendChild(mastodonFeedCreateElementTimeinfo(status, options, (isReblog ? false : status.url)));
statusElem.appendChild(accountElem);
// prepare content rendering
let showStatus = status;
if(isReblog) {
showStatus = status.reblog;
}
let contentWrapperElem = mastodonFeedCreateElement('div', 'contentWrapper' + (isReblog ? ' boosted' : ''));
// add boosted post meta info
if(isReblog) {
let boostElem = mastodonFeedCreateElement('div', 'account');
let boostAccountLink = mastodonFeedCreateElementAccountLink(showStatus.account);
boostElem.appendChild(boostAccountLink);
boostElem.appendChild(mastodonFeedCreateElementTimeinfo(showStatus, options, showStatus.url));
contentWrapperElem.appendChild(boostElem);
}
let contentElem = mastodonFeedCreateElement('div', 'content');
// handle content warnings
if(showStatus.sensitive || showStatus.spoiler_text.length > 0) {
let cwElem = mastodonFeedCreateElement('div', 'contentWarning');
if(showStatus.spoiler_text.length > 0) {
let cwTitleElem = mastodonFeedCreateElement('div', 'title');
cwTitleElem.innerHTML = showStatus.spoiler_text;
cwElem.appendChild(cwTitleElem);
}
let cwLinkElem = mastodonFeedCreateElement('a');
cwLinkElem.href = '#';
cwLinkElem.onclick = function() {
this.parentElement.style = 'display: none;';
this.parentElement.nextSibling.style = 'display: block;';
return false;
}
cwLinkElem.innerHTML = options.text.showContent;
cwElem.appendChild(cwLinkElem);
contentWrapperElem.appendChild(cwElem);
contentElem.style = 'display: none;';
}
// add regular content
let renderContent = showStatus.content;
// inject emojis
if(showStatus.emojis.length > 0) {
showStatus.emojis.forEach(function(emoji) {
renderContent = mastodonFeedInjectEmoji(renderContent, emoji);
});
}
contentElem.innerHTML += renderContent;
// handle media attachments
if(showStatus.media_attachments.length > 0) {
let mediaAttachmentsElem = mastodonFeedCreateElementMediaAttachments(showStatus, options);
contentElem.appendChild(mediaAttachmentsElem);
}
// handle preview card
if(options.showPreviewCards && showStatus.card != null) {
let cardElem = mastodonFeedCreateElementPreviewCard(showStatus.card);
contentElem.appendChild(cardElem);
}
contentWrapperElem.appendChild(contentElem);
statusElem.appendChild(contentWrapperElem);
rootElem.appendChild(statusElem);
}
}
if('_self' != options.linkTarget) {
rootElem.querySelectorAll('a').forEach(function(e) {
e.target = options.linkTarget;
});
}
}
const mastodonFeedLoad = function(url, elementId, options) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function() {
const statuses = xhr.response;
const rootElem = document.getElementById(elementId);
rootElem.innerHTML = '';
if (xhr.status === 200) {
if(options.excludeConversationStarters && statuses.length > 0) {
const filteredStatuses = [];
for(let i = 0; i < statuses.length; i++) {
let includeStatus = true;
if(statuses[i].mentions.length > 0) {
const statusContent = document.createElement('div');
statusContent.innerHTML = statuses[i].content;
const mentionUsername = statuses[i].mentions[0].acct.split('@')[0];
const plainTextContent = statusContent.textContent || statusContent.innerText;
if(plainTextContent.substring(1, ('@' + mentionUsername).length) == mentionUsername) {
includeStatus = false;
}
}
if(includeStatus) {
filteredStatuses.push(statuses[i]);
}
}
mastodonFeedRenderStatuses(filteredStatuses, rootElem, options);
}
else {
mastodonFeedRenderStatuses(statuses, rootElem, options);
}
}
else {
rootElem.appendChild(document.createTextNode(xhr.response.error));
}
};
xhr.send();
}
apiUrl = 'https://social.queereszentrumtuebingen.de/api/v1/accounts/01JFERHG3QG06PQ7QGYEZ6288P/statuses';
window.addEventListener("load", () => {
mastodonFeedLoad(
apiUrl,
"fedi-feed",
{
linkTarget: "_self",
showPreviewCards: true,
excludeConversationStarters: false,
text: {
boosted: false,
noStatuses: false,
viewOnInstance: "view on instance",
showContent: "Show content",
permalinkPre: "on",
permalinkPost: "",
edited: "(edited)",
},
localization: {
date: {
locale: "de-DE",
options: {},
}
}
}
);
});

174
src/style.css Normal file
View File

@ -0,0 +1,174 @@
:root {
--include-mastodon-feed-bg-light: rgba(100, 100, 100, 0.15);
--include-mastodon-feed-bg-dark: rgba(155, 155, 155, 0.15);
--include-mastodon-feed-accent-color: rgb(99, 100, 255);
--include-mastodon-feed-accent-font-color: rgb(255, 255, 255);
--include-mastodon-feed-border-radius: 0.25rem;
}
.include-mastodon-feed .status {
margin: 0.5rem 0 1.5rem;
border-radius: var(--include-mastodon-feed-border-radius);
padding: 0.5rem;
background: var(--include-mastodon-feed-bg-light);
}
.include-mastodon-feed .status a {
color: var(--include-mastodon-feed-accent-color);
text-decoration: none;
word-wrap: break-word;
}
.include-mastodon-feed .status a:hover {
text-decoration: underline;
}
.include-mastodon-feed .avatar {
height: 1.25rem;
border-radius: var(--include-mastodon-feed-border-radius);
vertical-align: top;
}
.include-mastodon-feed .account {
font-size: 0.8rem;
}
.include-mastodon-feed .account a {
display: inline-block;
}
.include-mastodon-feed .account .booster {
float: right;
font-style: italic;
}
.include-mastodon-feed .boosted .account>a:first-child,
.include-mastodon-feed .contentWarning a {
border-radius: var(--include-mastodon-feed-border-radius);
padding: 0.15rem 0.5rem;
background: var(--include-mastodon-feed-accent-color);
color: var(--include-mastodon-feed-accent-font-color);
}
.include-mastodon-feed .boosted .account>a:first-child:hover,
.include-mastodon-feed .contentWarning a:hover {
border-radius: var(--include-mastodon-feed-border-radius);
padding: 0.15rem 0.5rem;
background: var(--include-mastodon-feed-accent-font-color);
color: var(--include-mastodon-feed-accent-color);
text-decoration: none;
}
.include-mastodon-feed .contentWrapper.boosted {
margin: 0.5rem 0;
padding: 0.5rem;
background: var(--include-mastodon-feed-bg-light);
}
.include-mastodon-feed .contentWarning {
text-align: center;
margin: 1rem;
padding: 1rem;
}
.include-mastodon-feed .contentWarning .title {
font-weight: bold;
}
.include-mastodon-feed img.emoji {
height: 1rem;
}
.include-mastodon-feed .content .invisible {
display: none;
}
.include-mastodon-feed .media {
display: flex;
justify-content: space-around;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
margin: 1rem;
}
.include-mastodon-feed .media>div {
flex-basis: calc(50% - 0.5rem);
flex-grow: 1;
}
.include-mastodon-feed .media>.image {
font-size: 0.8rem;
font-weight: bold;
text-align: center;
}
.include-mastodon-feed .media>.image a {
border-radius: var(--include-mastodon-feed-border-radius);
display: block;
aspect-ratio: 1.618;
background-size: cover;
background-position: center;
}
.include-mastodon-feed .media>.image a:hover {
filter: contrast(110%) brightness(130%) saturate(130%);
}
.include-mastodon-feed .media>.gifv video {
max-width: 100%;
}
.include-mastodon-feed .card {
border-radius: var(--include-mastodon-feed-border-radius);
margin: 1rem 0.5rem;
}
.include-mastodon-feed .card iframe {
border-radius: var(--include-mastodon-feed-border-radius);
width: 100%;
height: 100%;
aspect-ratio: 2 / 1.25;
}
.include-mastodon-feed .card a {
border-radius: var(--include-mastodon-feed-border-radius);
display: block;
text-decoration: none;
color: #000;
}
.include-mastodon-feed.dark .card a {
color: #fff;
}
.include-mastodon-feed .card a:hover {
text-decoration: none;
background: var(--include-mastodon-feed-accent-color);
color: var(--include-mastodon-feed-accent-font-color);
}
.include-mastodon-feed .card .meta {
background: var(--include-mastodon-feed-bg-light);
font-size: 0.8rem;
padding: 1rem;
}
.include-mastodon-feed .card .image {
margin-bottom: 0.5rem;
text-align: center;
}
.include-mastodon-feed .card .image img {
max-width: 75%;
}
.include-mastodon-feed .card .title {
font-weight: bold;
}
.include-mastodon-feed.dark .status,
.include-mastodon-feed.dark .contentWrapper.boosted,
.include-mastodon-feed.dark .card {
background: var(--include-mastodon-feed-bg-dark);
}