Yeah I’ll definitely reach out if I need some help and guidance with it.
@cjeller1592 do you guys have a test blog that I can point to for testing? Something meant specifically for testing that has a few posts on it. I didn’t want to spam your web service with calls to get all posts from my blog every time I test something.
I wanted to share something @blake implemented on his blog that excited me the moment I saw it…
DYNAMIC TABLE OF CONTENTS ON BLOG POSTS!
He adds Custom JavaScript that looks for (sub)headers that creates a table of contents. It’s a sticky toc that follows the reader wherever they go and allows them to go to each section by clicking on the link in the toc.
Really great stuff and would love to show the JavaScript if that’s okay @blake!
That is pretty cool!
Ahh! SO cool that you saw that and thought to share it here! Thanks so much @cjeller1592!
My code’s a little bit spaghetti-ish and might be a little buggy! I’m 100% sure someone here could find a much cleaner solution! I wrote it so people using my tutorials/class could jump to the parts they need or go back to where they left off.
One more heads up before I put the code and explain it: you need to add <div id="toc"></div>
to posts where you want the table of contents to appear. I also recommend putting this element after the fold so that the script doesn’t generate a TOC for your home page. Again, I’m sure there’s a cleaner solution!
The code is:
CSS
#post-body {
max-width: 40rem;
}
body#post article ul, body#post article ul ul, body#post article li {
margin: 0;
}
#post nav a:not(.home), header nav a {
margin: 0;
}
#post nav a:not(.home):not(.sub-nav), header nav a {
margin: 0 0 0 1em;
}
@media only screen and (min-width: 60rem) {
#post .tutorial {
max-width: 60rem;
}
#article-container {
display: flex;
justify-content: space-between;
}
#table-of-contents, #spacer {
min-width: 18rem;
max-width: 18rem;
}
.sticky {
position: fixed;
top: 10px;
width: 100%;
}
}
JavaScript
var toc = document.createElement("nav");
if (document.getElementById("toc")) {
window.onscroll = function() {onScroll()};
document.getElementsByTagName("article")[0].classList.add("tutorial");
var body = document.getElementsByClassName("e-content")[0];
body.id = "article-container";
var article = document.createElement("div");
article.innerHTML = body.innerHTML;
document.getElementById("post-body").id = "";
article.id = "post-body";
article.classList = body.classList;
var contents = document.getElementById("toc");
toc.id = "table-of-contents";
toc.innerHTML = "<h3>Contents</h3>";
body.innerHTML = "";
body.appendChild(toc);
body.appendChild(article);
document.getElementById("toc").remove();
makeTableOfContents();
}
function makeTableOfContents() {
var headers = Array.from(document.querySelectorAll("h1,h2,h3,h4,h5,h6"));
var currentLevel = 3;
var lists = [];
var rootList = document.createElement("ul");
lists.push(rootList);
var currentList = 0;
toc.appendChild(rootList);
for (var i = 3; i < headers.length; i++) {
var level = headers[i].tagName.charAt(1);
if (level > currentLevel) {
var list = document.createElement("ul");
lists.push(list);
lists[currentList].appendChild(list);
currentList = lists.length - 1;
currentLevel = level;
}
else if (level < currentLevel) {
currentList -= 1;
currentLevel = level;
}
var listItem = document.createElement("li");
listItem.innerHTML = "<a class=\"sub-nav\" href=\"#" + headers[i].id + "\">" + headers[i].innerHTML + "</a>";
lists[currentList].appendChild(listItem);
}
}
var sticky = 150;
function onScroll() {
if (document.documentElement.scrollTop >= sticky) {
if (document.getElementById("spacer") === null) {
toc.classList.add("sticky");
var body = document.getElementsByClassName("e-content")[0];
var spacer = document.createElement("div");
spacer.id = "spacer";
body.insertBefore(spacer, document.getElementById("post-body"));
}
} else if (document.getElementById("spacer") !== null) {
toc.classList.remove("sticky");
document.getElementById("spacer").remove();
}
}
The general approach is:
Making the table of contents
- Make the original
article
element essentially a container for both the table of contents and a newdiv
, which I fill with thearticle
element’s contents (the post itself). I then empty the originalarticle
element and fill it with both the table of contents element and the new div. - Fill the table of contents element. Start by creating a root list
ul
element and add it to the list oful
elements. Then, iterate through every header element. (I think there could be a recursive solution here instead?) (I start ath3
elements and only care about the headers starting at the third one to avoid getting the blog name, the post title, and the “Contents” header.) For each header, if the header level (like h3, h4, etc.) is greater than the current level, so if this is a subheader of the previous header, create a newul
and add it to the list. Then add the new header linkli
that links to the associated header id (only possible because Write.as headers come with their own ids! Perfect!) to the currentul
. If the new header level is less, so this is a larger header than the last, just go to the previousul
in the list and add this new header link to that one. If it’s the same, just add it to the currentul
.
Making the table of contents sticky
edit: whoops, I didn’t know about this – https://davidwalsh.name/detect-sticky
- Bind the scroll event to a function called
onScroll
, so theonScroll
method will be called whenever the user scrolls. - In your
onScroll
function, if the current scroll position is lower on the page than your table of contents, add thesticky
CSS class to anchor it to the top of the page. But because thesticky
class anchors the TOC to the top of the page, you need a spacer element to fill its position in the article; otherwise the post contents would shift to center in the whole 60rem (when it’s supposed to stay shifted to the right). So create a new spacer element with the CSS idspacer
(associated with a width of 18rem, allowing up to 2rem of space between.) and put it before the new postdiv
. If the user scrolls back up, just undo that.
Design
- Make CSS that adjusts the page so that the TOC + content takes 60rem instead of the usual 40 to accommodate a TOC. Make sure the post header covers the whole 60rem. Ensure that the
sticky
element keeps the same width as the TOC so that the transition to the TOC being positionedfixed
is relatively seamless. On mobile, keep the TOC in place for now. Style the lists and list elements to look more natural as a table of contents (remove/adjust margins).
Really hope this is helpful for someone! Please let me know if you have a nicer solution! I’m happy to answer any questions y’all might have about the code.
Thanks again @cjeller1592! Was really cool to see that you liked it
Here is how to update a mailto link and add the title of the post as the subject.
First you need a mailto link of course:
<a id="mailtolink" href="mailto:youremail@mail.com">Send email</a>
Then this is the JavaScript that you can add to your Custom Javascript:
/* Add Subject to Mailto Link */
if (document.getElementById("mailtolink")) {
var title = document.getElementById("title").innerHTML;
if (title) {
var newHref = "mailto:youremail@mail.com?subject=" + title;
document.getElementById("mailtolink").setAttribute("href", newHref);
}
}
I just realized after writing this, that I don’t need to type in the email address again when setting the newHref value. I can use use the existing. Anyway, you get the idea.
To change the image that shows up in the “card” when you share a link to your post, you can hide an image in your post, say by using the Post Signature feature. You can add something like the html tag below:
<img src="yourImage.jpg" width="250" height="250" style="display: none;">
What that does is it will change the content for the og:image
meta property on your post. At the same time, since the style is set to display: none;
, the image won’t show up on your post. This is useful for those posts that don’t have an image in the first place, but you want a specific image to show up when sharing links to your site.
Upon further testing of the “card” image trick I’ve shared above, I’ve found that you don’t even need the width and height attributes at all. It is best to resize the image to something small, like say 256x256. Then you can just point to that image without having to specify the width and height attributes.
<img src="yourImage.jpg" style="display: none;">
This forum won’t let me add more replies to the Share what you are working on and using! thread, so you guys get a new post.
I’ve been thinking of ways to add a “Featured Post” feature on my sites. This is the easiest idea to implement. Using the Javascript for providing a link to a Random post, you can use the same approach to adding a Featured Post link to your site. You control what post to feature by simply changing the link in your custom Javascript. It will look something like this:
First create a placeholder post and pin it on your homepage. I created a post titled “ Featured Song ”. Then here is the custom Javascript that I use on my site:
/* Featured Song Link */
var a = document.querySelector('a[href="https://write.as/now-listening-to/featured-song"]');
if (a) {
a.setAttribute('href', 'https://nowlisteningto.dinobansigan.com/hate-me-sometimes');
a.setAttribute('title', 'Featured song for this month');
}
var b = document.querySelector('a[href="/now-listening-to/featured-song"]');
if (b) {
b.setAttribute('href', 'https://nowlisteningto.dinobansigan.com/hate-me-sometimes');
b.setAttribute('title', 'Featured song for this month');
}
var c = document.querySelector('a[href="https://nowlisteningto.dinobansigan.com/featured-song"]');
if (c) {
c.setAttribute('href', 'https://nowlisteningto.dinobansigan.com/hate-me-sometimes');
c.setAttribute('title', 'Featured song for this month');
}
Just remember to change the link to something else every month or every week, or however often you want to.
This is awesome @dino! I think having a “Featured Post” is great for first time readers.
I was first confused why there was an addition variable, but I see that you’re also setting the title
attribute too, so that the song will show up as “Featured song for this month.” Didn’t even think of changing the title
attribute of a post — thanks for sharing that!
PS: I made it so that your post shows up on this topic — don’t know why you couldn’t contribute to it. Maybe the forum software thought it was spam?
Thanks @cjeller1592. Yeah it was saying that I can’t post anymore since I already had 3 posts in a row
In preparation of coming out of my 2nd digital declutter, I’ve been trying to find ways to optimize how I use my music blog. One of the things I want to do is to share my post to my friends who are on FB. Since write.as doesn’t have a cross post option to FB, I do this manually. This means opening FB on my browser, creating a new FB post with a link to my blog post. I want to find ways to make this even quicker, possibly with me not even having to open up FB.
What I’ve come up with here, is a way to add an FB share button at the bottom of my posts. This button when clicked, will open up a new tab where all I have to do, is click the Post button and it will automatically share my blog post to FB. And after I click the Post button, it also automatically closes the new tab, which means I don’t even end up on FB after. This share button only shows up when I’m logged in, which means other readers won’t see it. This is perfect for me since I can quickly hit the share button after publishing a new post.
Here is the custom Javascript I am using:
/* Show FB Share Button only when logged-in to write.as */
var currentURL = window.location.href;
var isLoggedInToWA = currentURL.indexOf("https://write.as/now-listening-to/") != -1;
if (isLoggedInToWA && document.getElementById("post-body")) {
var shareUrl = currentURL.replace("write.as/now-listening-to", "nowlisteningto.dinobansigan.com");
var shareText = '<iframe src="https://www.facebook.com/plugins/share_button.php?href=URLTOSHARE&layout=button&size=small&width=68&height=20&appId" width="68" height="20" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true" allow="encrypted-media"></iframe>';
shareText = shareText.replace("URLTOSHARE", shareUrl);
document.getElementsByTagName("article")[0].insertAdjacentHTML('beforeend', shareText);
}
You can replace the urls above to make it work on your site. Also, if you remove the script that checks for isLoggedInToWA, then you will have enabled the FB share button on all your posts, and your readers can click it and instantly share to FB.
Here is what it looks like at the bottom of a post on my music blog:
Created a Blazor WASM app that can generate the contents for a Write.as archive page. You can find it on this site: https://apps.dinobansigan.com/. Just find the link to the Archive Page Generator on the nav bar.
The reason I’m linking to the homepage is due to the nature of the website, it is a Blazor single page application. Linking directly to the specific archive generator page results in an error, unless the website has already been loaded previously. So you have to go through the homepage to start up the app. I’m sure there’s a workaround to this (I’m still a noob when it comes to Blazor), but for now this will do.
Now for the app itself. It is a client side app, which means performance depends on your browser and machine. Building the archive page contents for a Write.as site with hundreds of posts can take awhile.
Thanks for creating this @dino! This is a fast and easy way to create an archive page. Love how it is set up to style if needed.
I am also interested in the Writing Topic app that’s also on your site. It looks like it takes the particular topics from your journal and randomly picks one for you to write about? That’s great! Is it just taking it from a static list or is it taking tags from your blog?
The reason I ask is that I could see this being a separate app that gives someone something to write about by grabbing a list of tags from posts on Read Write.as and giving them one as a “topic” to write about.
You are welcome!
It is taking from a static list that I hard-coded into the app. Getting tags from posts themselves sounds like an interesting idea.
Is that doable with the Write.as API? I know your Python wrapper library had some Read.Write.as functionality to it. But I never really looked into if that was possible with the Write.as API.
Added a header image to my music blog using the CSS script below:
@media only screen and (min-width: 800px) {
body#collection header {
padding-top: 2em;
text-align: center;
max-width: 100%;
margin: 0 0 0 0;
background-image: url('yourImage.jpg');
height: 572px;
background-repeat: no-repeat;
background-size: cover;
}
}
I’m sure someone can use this and make it even better. The media query is to remove the header image when viewing the site on a phone because I didn’t know how to handle it. Now that I think about it, I can probably just use a smaller image on smaller screens. Anyway, you get the idea.
Just sharing something I learned:
Just finished up the data entry for the LM Jargon File which also can be accessed at LMJargon
I’ve got the pinned entries in mostly the right order (after unpinning and repinning them in the order I wanted), but I’ve been fiddling with the CSS trying to move the nav header to the left of the page (as a sidebar) and then fiddling with the line-breaking of the header.
It seems as though this should be possible with CSS and not need some custom JavaScript, but I haven’t managed to find anything that works yet. Tip and tricks would be appreciated!