From da407c0772c5eca0ceb4a95bd176fc7dc5307660 Mon Sep 17 00:00:00 2001 From: moanos Date: Sun, 26 Nov 2023 22:00:42 +0100 Subject: [PATCH] Initial commit --- .woodpecker.yml | 14 +++ README.md | 9 ++ src/LICENSE.txt | 16 +++ src/index.html | 14 +++ src/script.js | 328 ++++++++++++++++++++++++++++++++++++++++++++++++ src/style.css | 174 +++++++++++++++++++++++++ 6 files changed, 555 insertions(+) create mode 100644 .woodpecker.yml create mode 100644 README.md create mode 100644 src/LICENSE.txt create mode 100644 src/index.html create mode 100644 src/script.js create mode 100644 src/style.css diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..b4a8e5f --- /dev/null +++ b/.woodpecker.yml @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d653d3 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Include Fedi + +Include Fedi allows you to embed a fediverse accounts post into a sites + +```html +
Loading...
+``` + +[include-mastodon-feed instance="social.queereszentrumtuebingen.de" account="01JFERHG3QG06PQ7QGYEZ6288P" limit=3 excludeReplies=true excludeBoosts=true text-permalinkPre=am date-locale=de-DE] diff --git a/src/LICENSE.txt b/src/LICENSE.txt new file mode 100644 index 0000000..be1cfd1 --- /dev/null +++ b/src/LICENSE.txt @@ -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. diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..dbb1cbc --- /dev/null +++ b/src/index.html @@ -0,0 +1,14 @@ + + + + + + + + + +
Loading...
+ + + \ No newline at end of file diff --git a/src/script.js b/src/script.js new file mode 100644 index 0000000..8743184 --- /dev/null +++ b/src/script.js @@ -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
'; + 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 + ':', ''); +} + +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: {}, + } + } + } + ); +}); diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..fa5d416 --- /dev/null +++ b/src/style.css @@ -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); +} \ No newline at end of file