Am I In Over My Head || Search Filters without CSM on Neocities

Talk about your website ideas, web design, coding, software and hardware.
Post Reply
Ackasi
Newbie
Reactions:
Posts: 4
Joined: Fri Jan 13, 2023 12:54 am
Pronouns: any

Am I In Over My Head || Search Filters without CSM on Neocities

Post by Ackasi »

The crux of this project is that I'm extremely, extremely stubborn.

I don't use external places to host my artwork and writing. Things like AO3 aren't for me, but that's not really the point of this post.

I've been updating my site, https://ackasi.neocities.org/, specifically the fic portion, currently.

If you can see I've intergraded a lot of fantastic community tools like Zonelets! but for this specific page, I need a filter system that allows for various drop down selectors based on different "tag" (as in ao3 or tumblr tags) in order to organize my pieces of original and fan writing.

I've browsed some codepens and I'm honestly not feeling confident in the deviations of the system I've seen (I've tested...so, so many.) I think for the most part, I understand the functional HTML and CSS for my site, the Javascript (or in some cases I researched, using specific jquery plugins.

I simply want a system to filter the vast volume of writing I have. My past systems haven't been great but again, this is a project of stubbornness, but I'm not stubborn enough to ask for help.
User avatar
RogerMexico
Websurfer
Reactions:
Posts: 71
Joined: Sun Feb 19, 2023 4:36 pm
Pronouns: He/Him

Re: Am I In Over My Head || Search Filters without CSM on Neocities

Post by RogerMexico »

Technically speaking, you can definitely do that.

What you would do is make an array of objects that represent the links to your stories that include an array of tags.

Then in your dropdown you have some event that would trigger a filter and redraw the links.

I can see if I can make a CodePen if it might help?
Ackasi
Newbie
Reactions:
Posts: 4
Joined: Fri Jan 13, 2023 12:54 am
Pronouns: any

Re: Am I In Over My Head || Search Filters without CSM on Neocities

Post by Ackasi »

That would be incredibly helpful! I have an existing array that just formats the links with zonelets, so there might be something to do there.
User avatar
RogerMexico
Websurfer
Reactions:
Posts: 71
Joined: Sun Feb 19, 2023 4:36 pm
Pronouns: He/Him

Re: Am I In Over My Head || Search Filters without CSM on Neocities

Post by RogerMexico »

https://codepen.io/drewknab/pen/WNgzVNe

Have a look at the CodePen and let me know if you have any questions/concerns.
Ackasi
Newbie
Reactions:
Posts: 4
Joined: Fri Jan 13, 2023 12:54 am
Pronouns: any

Re: Am I In Over My Head || Search Filters without CSM on Neocities

Post by Ackasi »

Okay! I think this looks right on paper, my question is if I can assign multiple categories of filter to each piece of writing that would be linked, and have separate drop-downs per tag category?

Example of my categories would be like: rating, media properties, and content warning tags.
User avatar
RogerMexico
Websurfer
Reactions:
Posts: 71
Joined: Sun Feb 19, 2023 4:36 pm
Pronouns: He/Him

Re: Am I In Over My Head || Search Filters without CSM on Neocities

Post by RogerMexico »

Gotcha, that complicates it a little bit. No, I don't reckon you could.

I think what you'd want to do then is have several selects that you can set and then a button to actually trigger the filter.

I updated the CodePen to handle the multiple filter options.
Ackasi
Newbie
Reactions:
Posts: 4
Joined: Fri Jan 13, 2023 12:54 am
Pronouns: any

Re: Am I In Over My Head || Search Filters without CSM on Neocities

Post by Ackasi »

This works pretty much perfectly! One last question, is there any I could integrate an existing array? Here's my script for reference.

Code: Select all

/*Welcome to the script file! Your 1st time here, you should update
  the basic info section to include your name and website/social 
  media link (if desired). Most of the time, you will just come
  here to update the posts array. However, you can also edit or
  add your own scripts to do whatever you like!*/

//TABLE OF CONTENTS
  // 1. Basic Info
  // 2. Posts Array
  // 3. Creating HTML Sections to Be Inserted (Header, Footer, etc)
  // 4. Inserting the Sections Into our Actual HTML Pages

//-----------------------------

//==[ 1. BASIC INFO ]==

let blogName = "Ave's Fic Archive";
let authorName = "Avery";
let authorLink = "ackasi.neocities.org"; // Enter your website, social media, etc. Some way for people to tell you they like your blog! (Leaving it empty is okay too)

//-----------------------------

//==[ 2. POSTS ARRAY ]==

/*Each time you make a new post, add the filepath here at the top of postsArray.
  This will cause all the right links to appear and work.
  NOTE: It's important to follow this exact naming convention, because the scripts
  below are expecting it ( 'posts/YYYY-MM-DD-Title-of-Your-Post.html', ). You can
  alter the scripts if you want to use a different naming convention*/
/*UPDATE: as of version 1.3, you may omit the date if you would like. But if you
  use a date it must still follow that format.*/

let postsArray = [
//[ "posts/2020-11-10-Special-Characters-Example.html", encodeURI( 'Spéci@l "Character\'s" Examp|e' ) ]
//[ "posts/2020-11-10-My-Third-Post-Example.html" ],
//[ "posts/2020-11-10-My-Second-Post-Example.html" ],
[ "posts/2020-11-10-Post-Template.html" ],
["posts/2022-07-07-Black-Hole-Home.html"],
["posts/2022-07-26-Bleed.html"]]

//-----------------------------

//==[ 3. CREATING HTML SECTIONS TO BE INSERTED ]==

let url = window.location.pathname;

//The date format to look for is 4 digits, hyphen, 2 digits, hyphen, 2 digits, hyphen.
const postDateFormat = /\d{4}\-\d{2}\-\d{2}\-/;

//Check if you are in posts (if so, the links will have to go up a directory)
let relativePath = ".";
if ( url.includes("posts/") ) {
  relativePath = "..";
}

//Write the Header HTML, a series of list items containing links.
let headerHTML = '<ul> <li><a href="' + relativePath + '/index.html">Home</a></li>' + 
'<li><a href="' + relativePath + '/archive.html">Archive</a></li>' +
'<li><a href="' + relativePath + '/about.html">About</a></li> </ul>';

//Write the Footer HTML, which has information about the blog.
let footerHTML = "<hr><p>" + blogName + " is written by <a href='" + authorLink + "'>" + authorName + "</a>, built with <a href='https://zonelets.net/'>Zonelets</a>, design by <a href='http://almostsweet.neocities.org/about.html'>almost sweet resources</a>, and hosted by <a href='https://neocities.org/'>Neocities!</a>. Consider donating to my <a href='https://ko-fi.com/ackasi'>Ko-Fi.</a></p>";

//To do the following stuff, we want to know where we are in the posts array (if we're currently on a post page).
let currentIndex = -1;
let currentFilename = url.substring(url.lastIndexOf('posts/'));
//Depending on the web server settings (Or something?), the browser url may or may not have ".html" at the end. If not, we must add it back in to match the posts array. (12-19-2022 fix)
if ( ! currentFilename.endsWith(".html") ) {
    currentFilename += ".html";
}
let i;
for (i = 0; i < postsArray.length; i++) {
  if ( postsArray[i][0] === currentFilename ) {
    currentIndex = i;
  }
}

//Convert the post url to readable post name. E.g. changes "2020-10-10-My-First-Post.html" to "My First Post"
//Or pass along the "special characters" version of the title if one exists
function formatPostTitle(i) {
  // Check if there is an alternate post title
  if ( postsArray[i].length > 1 ) {
    //Remember how we had to use encodeURI for special characters up above? Now we use decodeURI to get them back.
    return decodeURI(postsArray[i][1]);
  } else { 
  //If there is no alternate post title, check if the post uses the date format or not, and return the proper title
	if (  postDateFormat.test ( postsArray[i][0].slice( 6,17 ) ) ) {
	  return postsArray[i][0].slice(17,-5).replace(/-/g," ");
    } else {
      return postsArray[i][0].slice(6,-5).replace(/-/g," ");
    }
  }
}

//Get the current post title and date (if we are on a post page)
let currentPostTitle = "";
let niceDate = "";
if ( currentIndex > -1 ) {
  currentPostTitle = formatPostTitle( currentIndex );
  //Generate the "nice to read" version of date
  if (  postDateFormat.test ( postsArray[currentIndex][0].slice( 6,17 ) ) ) {
    let monthSlice = postsArray[currentIndex][0].slice( 11,13 );
    let month = "";
    if ( monthSlice === "01") { month = "Jan";}
    else if ( monthSlice === "02") { month = "Feb";}
    else if ( monthSlice === "03") { month = "Mar";}
    else if ( monthSlice === "04") { month = "Apr";}
    else if ( monthSlice === "05") { month = "May";}
    else if ( monthSlice === "06") { month = "Jun";}
    else if ( monthSlice === "07") { month = "Jul";}
    else if ( monthSlice === "08") { month = "Aug";}
    else if ( monthSlice === "09") { month = "Sep";}
    else if ( monthSlice === "10") { month = "Oct";}
    else if ( monthSlice === "11") { month = "Nov";}
    else if ( monthSlice === "12") { month = "Dec";}
	niceDate = postsArray[currentIndex][0].slice( 14,16 ) + " " + month + ", " + postsArray[currentIndex][0].slice( 6,10 );
  }
}

//Generate the Post List HTML, which will be shown on the "Archive" page.

function formatPostLink(i) {
  let postTitle_i = "";
  if ( postsArray[i].length > 1 ) {
    postTitle_i = decodeURI(postsArray[i][1]);
  } else {
	if (  postDateFormat.test ( postsArray[i][0].slice( 6,17 ) ) ) {
	  postTitle_i = postsArray[i][0].slice(17,-5).replace(/-/g," ");
    } else {
      postTitle_i = postsArray[i][0].slice(6,-5).replace(/-/g," ");
    }
  }
  if (  postDateFormat.test ( postsArray[i][0].slice( 6,17 ) ) ) {
    return '<li><a href="' + relativePath + '/'+ postsArray[i][0] +'">' + postsArray[i][0].slice(6,16) + " \u00BB " + postTitle_i + '</a></li>';
  } else {
    return '<li><a href="' + relativePath + '/'+ postsArray[i][0] +'">' + postTitle_i + '</a></li>';
  }
}

let postListHTML = "<ul>";
for ( let i = 0; i < postsArray.length; i++ ) {
  postListHTML += formatPostLink(i);
}
postListHTML += "</ul>";

//Generate the Recent Post List HTML, which can be shown on the home page (or wherever you want!)
let recentPostsCutoff = 3; //Hey YOU! Change this number to set how many recent posts to show before cutting it off with a "more posts" link.
let recentPostListHTML = "<h2>Recent Posts:</h2><ul>";
let numberOfRecentPosts = Math.min( recentPostsCutoff, postsArray.length );
for ( let i = 0; i < numberOfRecentPosts; i++ ) {
  recentPostListHTML += formatPostLink(i);
}
/*If you've written more posts than can fit in the Recent Posts List,
  then we'll add a link to the archive so readers can find the rest of
  your wonderful posts and be filled with knowledge.*/
if ( postsArray.length > recentPostsCutoff ) {
  recentPostListHTML += '<li class="moreposts"><a href=' + relativePath + '/archive.html>\u00BB more posts</a></li></ul>';
} else {
  recentPostListHTML += "</ul>";
}

//Generate the Next and Previous Post Links HTML
let nextprevHTML = "";
let nextlink = "";
let prevlink = "";

/*If you're on the newest blog post, there's no point to
 a "Next Post" link, right? And vice versa with the oldest 
 post! That's what the following code handles.*/
if ( postsArray.length < 2 ) {
  nextprevHTML = '<a href="' + relativePath + '/index.html">Home</a>';
} else if ( currentIndex === 0 ) {
  prevlink = postsArray[currentIndex + 1][0];
  nextprevHTML = '<a href="' + relativePath + '/index.html">Home</a> | <a href="'+ relativePath + '/' + prevlink +'">Previous Post \u00BB</a>';
} else if ( currentIndex === postsArray.length - 1 ) {
  nextlink = postsArray[currentIndex - 1][0];
  nextprevHTML = '<a href="' + relativePath + '/' + nextlink +'">\u00AB Next Post</a> | <a href="' + relativePath + '/index.html">Home</a>';
} else if ( 0 < currentIndex && currentIndex < postsArray.length - 1 ) {
  nextlink = postsArray[currentIndex - 1][0];
  prevlink = postsArray[currentIndex + 1][0];
  nextprevHTML = '<a href="' + relativePath + '/'+ nextlink +'">\u00AB Next Post</a> | <a href="' + relativePath + '/index.html">Home</a> | <a href="' + relativePath + '/'+ prevlink +'">Previous Post \u00BB</a>';
}

//-----------------------------

//==[ 4. INSERTING THE SECTIONS INTO OUR ACTUAL HTML PAGES ]==

/*Here we check if each relevant div exists. If so, we inject the correct HTML!
  NOTE: All of these sections are optional to use on any given page. For example, if there's 
  one particular blog post where we don't want the footer to appear, 
  we simply don't put a <div id="footer"> on that page.*/

if (document.getElementById("nextprev")) {
  document.getElementById("nextprev").innerHTML = nextprevHTML;
}
if (document.getElementById("postlistdiv")) {
  document.getElementById("postlistdiv").innerHTML = postListHTML;
}
if (document.getElementById("recentpostlistdiv")) {
  document.getElementById("recentpostlistdiv").innerHTML = recentPostListHTML;
}
if (document.getElementById("header")) {
  document.getElementById("header").innerHTML = headerHTML;
}
if (document.getElementById("blogTitleH1")) {
  document.getElementById("blogTitleH1").innerHTML = blogTitle;
}
if (document.getElementById("postTitleH1")) {
  document.getElementById("postTitleH1").innerHTML = currentPostTitle;
}
if (document.getElementById("postDate")) {
  document.getElementById("postDate").innerHTML = niceDate;
}
if (document.getElementById("footer")) {
  document.getElementById("footer").innerHTML = footerHTML;
}

//Dynamically set the HTML <title> tag from the postTitle variable we created earlier
//The <title> tag content is what shows up on browser tabs
if (document.title === "Blog Post") {
  document.title = currentPostTitle;
}
// Find and store our HTML in JS variables
// We can use $ to specify that it's a DOM Element
const $linkList = document.querySelector("#link-list");
const $tagSelect = document.querySelector("#tag-select");
const $ratingSelect = document.querySelector("#rating-select");
const $mediaSelect = document.querySelector("#media-select");
const $filterButton = document.querySelector("#filter-button");

// Define our array of links
const links = [
  {
    url: "http://example.org/",
    title: "A Descriptive Horror Title",
    rating: "4",
    mediaProperties: [],
    tags: [
      "horror",
      "contemporary"
    ]
  },
  {
    url: "http://example.org/",
    title: "A Descriptive Comedy Title",
    rating: "3",
    mediaProperties: [],
    tags: [
      "comedy",
      "contemporary"
    ]
  },
  {
    url: "http://example.org/",
    title: "A Descriptive Drama Title",
    rating: "5",
    mediaProperties: [
      "Some Media Property"
    ],
    tags: [
      "drama",
      "contemporary"
    ]
  },
];

// Specify the HTML we're going to create
// We define it here so we only have to do it once.
const htmlMap = item => `<li><a href="${item.url}">${item.title}</a></li>`

// Add an event to $filterButton
// Click triggers when we click the $filterButton
$filterButton.addEventListener("click", event => {  
  $linkList.innerHTML = links
    .filter(item => {
      tags = $tagSelect.value === "all" 
        ? true 
        : item.tags.includes($tagSelect.value);
 
      ratings = $ratingSelect.value === "all"
        ? true
        : item.rating === $ratingSelect.value;
  
      mediaProperties = $mediaSelect.value === "all"
        ? true
        : item.mediaProperties.includes($mediaSelect.value);
    
      return tags && ratings && mediaProperties;
    })
    .map(htmlMap)
    .join("")
});

// Set up our initial state of all links
$linkList.innerHTML = links.map(htmlMap).join("");
User avatar
RogerMexico
Websurfer
Reactions:
Posts: 71
Joined: Sun Feb 19, 2023 4:36 pm
Pronouns: He/Him

Re: Am I In Over My Head || Search Filters without CSM on Neocities

Post by RogerMexico »

The postsArray in your current script looks like it’s an array of strings. To work the way mine is working you would have to use a similar array of objects to the one I’m using instead of just strings. Filtering on additional data requires more data.

I don’t think you can drop my script into your script without modifying either? They do basically the same thing in a fairly incompatible way.

I can take a look at it later and see what that might take?
User avatar
PawSense
Newbie
Reactions:
Posts: 11
Joined: Tue Jan 24, 2023 5:56 pm
Pronouns: she/they
Website: https://furret.org/

Re: Am I In Over My Head || Search Filters without CSM on Neocities

Post by PawSense »

RogerMexico wrote: Wed Mar 15, 2023 1:27 am https://codepen.io/drewknab/pen/WNgzVNe

Have a look at the CodePen and let me know if you have any questions/concerns.
just wanted to say, i had my own questions about how search filtering could work and looking through this really helped! thanks a ton for your time putting it together!! :)
Starfia
Websurfer
Reactions:
Posts: 62
Joined: Fri Jan 27, 2023 6:37 pm
Website: https://stevebarnes.org

Re: Neocities search filters

Post by Starfia »

Hey, Ackasi,

I'm super-late to responding and I'm sure you've found a good answer for you, but I wanted to say I thought about the problem, and I don't think you're too ambitious for striving to solve it even if it can't be optimized within those constraints, since that's the kind of force that has always evolved web standards.

I think the main constraint you're facing is that with Neocities, on your "table of contents" page, you'd have to manually maintain some list of your post URLs in association with their tags, in parallel with the posts themselves. But with Zonelets, you're responsible for maintaining a parallel array of links anyway, aren't you? If you're willing to somehow add attributes to the links, then you have some means for filtering in JavaScript, and that should constitute the basic functionality as in that last CodePen example, though you have some freedom to design exactly how that's done.

I think I'm doing something like what you're after on my blog, though I have a cluster of buttons rather than drop-down menus. This isn't "external" since I write my whole site, but I'm also allowed to write server code whereby the organization is done automatically. Ultimately, it still generates custom HTML which JavaScript is looking at, like it would in Neocities. For efficiency, I'm also using the technique of expressing combinations of tags in code as single integers. If you do get ambitious and comfortable enough, maybe you're ready to graduate from Neocities to something like that.
Post Reply