Dark mode switch icon Light mode switch icon

Podcast Experiment with 1W3.io No-Code Builder Customization Options

7 min read

When first jumping into web3, my journey began with research into hosting websites on the blockchain. I figured there had to be a way to store content on the blockchain, and not rely on a single point of failure in a server somewhere. There are ways to achieve redundancy and availability using services through AWS or CDNs. It’s kind of overkill for what I was trying to accomplish in the event I forget to pay for a cloud server or if my hosting company goes away.

Of course there are still advantages to having a server run backend code, especially in the cases of extremely dynamic or complex websites. However, there are a lot of use cases for just running a static website. Doing so will allow you to deploy a site to distributed storage networks such as IPFS or Arweave and not depend on any hosting servers.

No-code buidlers for decentralized websites

Previously I wrote a guide on how to create a decentralized podcast on IPFS with an ENS name. After creating that proof of concept I wanted to experiment with creating a podcast with a no-code builder. Though my previous guide isn’t too technically involved, it still is not for the average user. For instance you have to install IPFS Desktop, or know how to upload files into IPFS and then reference those CIDs in your site’s file. You also have to edit a few lines of code, which may be intimidating to some. I wondered if there was a way to create a podcast using a no-code builder like 1W3 that specifically works with distributed storage networks like IPFS and Arweave.

It’s kind of a “hacky” workaround, but it works.

Login to 1W3 and add some blocks

Go to the 1W3 Dashboard and login. Next, add an Image block.

Image description

Image description

Add the image for the first episode / song.

Upload an image you want displayed while the episode or song plays. This image will also be used in the podcast feed.

Image description

Add your audio file (.mp3)

Immediately below the Image block, add an Audio block.

Image description

Upload an .mp3 file. Be sure to give it a title.

Image description

Add a Paragraph block

This will be used for the episode / song description.

Image description

Enter in a description.

Image description

Here is what it should look like so far.

Image description

Add more Image, Audio, and Paragraph blocks.

Repeat the process. Each time adding an Image, Audio, and Paragraph block in that order.

Image description

I added 3 total images and audio files. Notice how it scrolls which doens’t look all that great. No worries, we will introduce a custom script to reformat things.

Image description

Customize with JS and CSS

Let’s fix the player. Click on Customizations in 1W3 then click on Advanced. Copy in the following custom CSS and custom JS.

Image description

custom JS

<script>
document.addEventListener('DOMContentLoaded', function() {
    // Hide empty div elements at the top
    document.querySelectorAll('.col-12.my-2 .card').forEach(function (div) {
        if (!div.hasChildNodes()) {
            div.style.display = 'none';
        }
    });

    var slideshowContainer = document.createElement('div');
    slideshowContainer.id = 'slideshow-container';

    var pairs = [];
    var pair;
    var images = document.querySelectorAll('.col-12.my-2 img.img-fluid.rounded');
    var players = document.querySelectorAll('.col-12.my-2 audio');
    var paragraphs = document.querySelectorAll('.col-12.my-2 .card-body');

    if (images.length === players.length && images.length === paragraphs.length) {
        players.forEach(function (player, index) {
            pair = document.createElement('div');
            pair.classList.add('pair');
            pair.style.display = index === 0 ? 'block' : 'none';

            // Create and append the title as a <span>
            var title = document.createElement('span');
            title.innerText = player.getAttribute('title'); // Using audio title
            title.style.color = 'white';
            title.style.fontWeight = 'bold';

            // Append elements to the pair
            pair.appendChild(images[index].parentElement);
            pair.appendChild(player.parentElement);
            pair.appendChild(title); // Title below audio player, above paragraph
            pair.appendChild(paragraphs[index]);

            pairs.push(pair);
            slideshowContainer.appendChild(pair);
        });
    }

    var nextButton = document.createElement('button');
    nextButton.id = 'next-button';
    nextButton.innerHTML = 'Next';
    nextButton.onclick = function() { navigateSlide(1); };

    var prevButton = document.createElement('button');
    prevButton.id = 'prev-button';
    prevButton.innerHTML = 'Prev';
    prevButton.onclick = function() { navigateSlide(-1); };

    slideshowContainer.appendChild(prevButton);
    slideshowContainer.appendChild(nextButton);

    var downloadButton = document.createElement('button');
    downloadButton.id = 'download-button';
    var iconElem = document.createElement('i');
    iconElem.className = 'far fa-arrow-alt-circle-down';
    downloadButton.appendChild(iconElem);
    var textNode = document.createTextNode(' RSS');
    downloadButton.appendChild(textNode);
    downloadButton.className = 'rss-button';
    slideshowContainer.appendChild(downloadButton);

    document.getElementById('links').appendChild(slideshowContainer);

    downloadButton.onclick = generateRSS;

    function navigateSlide(direction) {
        var activeIndex = 0;
        pairs.forEach(function(pair, index) {
            if (pair.style.display === 'block') {
                activeIndex = index;
            }
        });
        var newIndex = (activeIndex + direction) % pairs.length;
        if (newIndex < 0) newIndex += pairs.length;

        pairs[activeIndex].style.display = 'none';
        pairs[newIndex].style.display = 'block';
    }

    function generateRSS(event) {
        event.preventDefault();

        const podcast = {
            title: "My Podcast Title",
            author: "Author Name",
            description: "Podcast Description",
        };

        let episodes = [];
        let imagesHref = [];
        let episodeDescriptions = [];
        let episodeTitles = [];

        for (let i = 0; i < players.length; i++) {
            const audioSrc = players[i].querySelector('source').src;
            const imageSrc = images[i].src;
            const episodeDescription = paragraphs[i].textContent.trim();
            const episodeTitle = players[i].getAttribute('title'); // Using audio title

            episodes.push(audioSrc);
            imagesHref.push(imageSrc);
            episodeDescriptions.push(episodeDescription);
            episodeTitles.push(episodeTitle);
        }

        const rssHeader = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"><channel><title>${podcast.title}</title><link>${window.location.href}</link><language>en</language><itunes:author>${podcast.author}</itunes:author><description>${podcast.description}</description>`;
        let rssItems = '';

        for (let i = 0; i < episodes.length; i++) {
            const episodeTitle = episodeTitles[i];
            const episodeDescription = episodeDescriptions[i];
            const pubDate = new Date().toUTCString();

            rssItems += `<item><title>${episodeTitle}</title><description>${episodeDescription}</description><enclosure url="${episodes[i]}" length="0" type="audio/mpeg" /><itunes:image href="${imagesHref[i]}" /><pubDate>${pubDate}</pubDate></item>`;
        }

        const rssFooter = `</channel></rss>`;
        const rss = new Blob([rssHeader + rssItems + rssFooter], { type: 'application/rss+xml' });

        const url = URL.createObjectURL(rss);
        var a = document.createElement("a");
        document.body.appendChild(a);
        a.style = "display: none";
        a.href = url;
        a.download = 'podcast.rss';
        a.click();
        window.URL.revokeObjectURL(url);
    }
});

</script>

Custom CSS

#next-button,
#prev-button {
    display: inline-block;
    margin: 10px 5px;
    padding: 10px 20px;
    cursor: pointer;
    background-color: #f0f0f0;
    border: 1px solid #ccc;
    color: #333;
    width: 150px;
    border-radius: 6px;
    height: auto;
    line-height: normal;
    text-align: center;
}

button,
.rss-button {
    border: 0;
    width: 150px;
    padding: 10px 20px;
    color: #fff;
    font-weight: bold;
    font-size: 1em;
    background: #7a807d;
    border-radius: 6px;
    height: auto;
    line-height: normal;
    text-align: center;
}

button:hover,
#next-button:hover,
#prev-button:hover {
    background: #7a807d;
    transform: none;
    animation: none;
}

/* Remove hover effect for the rss-button */
.rss-button:hover {
    background: #7a807d;
}

Preview the site and publish

As you can see we’ve used Javascript and some custom CSS styling to reformat the image, audio, and paragraph blocks into groupings. It also has a podcast feed generator for downloading of the podcast.RSS file based on decentralized content it finds.

Image description

Connect the site to an ENS name

Image description

Here’s a published example of what we’ve done above: 1w3podcast.showmehow.eth.limo

Future of web templates as NFTs

1W3 has recently begun experimenting with alowing creators to mint an NFT from a template they have designed. If another user owns that NFT template, they can use that in their own decentralized website design.

This is a really interesting idea to facilate growth of creator economies! Check out the GitHub repo for more this experiment.

Originally published on by Zadok7.eth