Skip to content

Add list pagination functionality #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion src/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,34 @@ function removeDirectory(dirPath) {
}

function buildPages(pagesDir, buildDir, url) {
paginationList = [];
// check if it's pagination page
const staticJsonPath = path.join(currentDirectory, 'static.json');
if (fs.existsSync(staticJsonPath)) {
const staticJsonContent = fs.readFileSync(staticJsonPath, 'utf8');
staticJSON = JSON.parse(staticJsonContent);
if (staticJSON.hasOwnProperty('paginationList')) {
paginationList = staticJSON.paginationList
}
}
const entries = fs.readdirSync(pagesDir, { withFileTypes: true });

for (const entry of entries) {
const entryPath = path.join(pagesDir, entry.name);
if (entry.isDirectory()) {
const newBuildDir = path.join(buildDir, entry.name);
fs.mkdirSync(newBuildDir, { recursive: true });
buildPages(entryPath, newBuildDir, url);
} else if (entry.isFile() && entry.name.endsWith('.html')) {
buildFile(entryPath, buildDir, url);
routeBaseName = path.basename(entry.name,'.html')
// check this route in static.json
let pagination = getPageSizeForRoute("/" + routeBaseName, paginationList)

if ( paginationList == [] || !pagination ){
buildFile(entryPath, buildDir, url);
}else{
buildPaginationFile(entryPath, buildDir, url, routeBaseName, pagination);
}
}
}
}
Expand Down Expand Up @@ -122,4 +141,40 @@ function buildFile(filePath, buildDir, url){
if(content != null){
fs.writeFileSync(filePath, content);
}
}
function buildPaginationFile(filePath, buildDir, url, keyName, pagination){
containsMagicLast = false
pageNo = 0;
while (!containsMagicLast){
pageContent = parser.processFile(filePath, true, url, pageNo, pagination)
pageContent = parser.parseURLs(pageContent, url);
if ( pageContent.includes("<div id='last-page-marker'></div>")){
containsMagicLast = true
}
if (pageNo == 5){
containsMagicLast = true
}
// Generate the file path for the current page
// let pageFileDir = path.join(buildDir, keyName, 'pgn', `${pageNo}`);
let pageFileDir = path.join(buildDir, keyName, `${pageNo}`);

fs.mkdirSync(pageFileDir, { recursive: true });
fs.writeFileSync(path.join(pageFileDir, 'index.html'), pageContent);
// default : no /pageNo
if (pageNo == 0){
const newBuildDir = path.join(buildDir, keyName);
fs.writeFileSync(path.join(newBuildDir, 'index.html'), pageContent);
}
pageNo ++

}

}
function getPageSizeForRoute(routeToCheck, paginationList) {
// Use the Array.prototype.find() method to find the first object that matches the given route.
const item = paginationList.find(item => item.route === routeToCheck);

// Check if an item was found with the specified route; if so, return its pageSize.
// If no item is found (item is undefined), return null.
return item ? item : null;
}
49 changes: 44 additions & 5 deletions src/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ module.exports = {

},
handleRequest(req, res, url){
const route = req.path === '/' ? '/index' : req.path;
let route = req.path === '/' ? '/index' : req.path;

// First we are going to check if we have a content file in this location
let contentPath = path.join(currentDirectory, './content', route + '.md');
Expand All @@ -114,21 +114,52 @@ module.exports = {
return res.send(contentFile);
}

// If we made it this far we want to now check if the static html file exists
// Handle pagination if specified in 'static.json'.
paginationList = [];
const staticJsonPath = path.join(currentDirectory, 'static.json');
if (fs.existsSync(staticJsonPath)) {
const staticJsonContent = fs.readFileSync(staticJsonPath, 'utf8');
staticJSON = JSON.parse(staticJsonContent);
if (staticJSON.hasOwnProperty('paginationList')) {
paginationList = staticJSON.paginationList
}
}

// Regex to extract page number from the route.
let pageNo = null;
// const pageRegex = /\/pgn\/(\d+)/;
const pageRegex = /\/(\d+)/;

isPaginationRoute = false;
const containsPageNo = route.match(pageRegex); // route = /posts/page/0
if ( containsPageNo ){
pageNo = parseInt(containsPageNo[1], 10);
isPaginationRoute = true
route = route.replace(pageRegex, '')
}

// Check for pagination details in the static JSON configuration.
let pagination = this.getPageSizeForRoute(route, paginationList)

if ( isPaginationRoute || pagination ){
pageNo = isPaginationRoute ? pageNo : 0
}

// Define paths to page files based on the current directory and the route.
let pagePath = path.join(currentDirectory, './pages', route + '.html');
let pagePathIndex = path.join(currentDirectory, './pages', route, '/index.html');
let pageContent = null;

// Check if the specified HTML file or its index exists and process it if found.
if (fs.existsSync(pagePath)) {
pageContent = parser.processFile(pagePath);
pageContent = parser.processFile(pagePath, false, 'relative', pageNo, pagination);

} else if(fs.existsSync(pagePathIndex)) {
pageContent = parser.processFile(pagePathIndex);
pageContent = parser.processFile(pagePathIndex, false, 'relative', pageNo, pagination);
}

if (pageContent != null) {
pageContent = parser.parseURLs(pageContent, url);

return res.send(pageContent);
}

Expand Down Expand Up @@ -165,5 +196,13 @@ module.exports = {

server.listen(port);
});
},
getPageSizeForRoute(routeToCheck, paginationList) {
// Use the Array.prototype.find() method to find the first object that matches the given route.
const item = paginationList.find(item => item.route === routeToCheck);

// Check if an item was found with the specified route; if so, return its pageSize.
// If no item is found (item is undefined), return null.
return item ? item : null;
}
}
134 changes: 96 additions & 38 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ let isContentFile = false;
let env = require('./env.js');

module.exports = {
processFile(filePath, build=false, url='relative') {


processFile(filePath, build=false, url='relative', pageNo=null, pagination=null) {

let page = this.getPage(filePath);

const layoutTagExists = /<layout[^>]*>[\s\S]*?<\/layout>/.test(page);
Expand All @@ -30,8 +29,10 @@ module.exports = {

// replace {slot} with content inside of Layout
layout = layout.replace('{slot}', this.parseIncludeContent(this.getPageContent(page)));

processedContentLoops = this.processContentLoops(this.parseShortCodes(this.replaceAttributesInLayout(layout, layoutAttributes), url, build), filePath);

page = this.processCollectionLoops(this.processContentLoops(this.parseShortCodes(this.replaceAttributesInLayout(layout, layoutAttributes), url, build), filePath), filePath);
page = this.processCollectionLoops(processedContentLoops, filePath, pageNo, pagination);

page = this.processCollectionJSON(page);
}
Expand Down Expand Up @@ -450,71 +451,122 @@ module.exports = {
fs.writeFileSync(filePath, jsonData);
},

processCollectionLoops(template, filePath) {
processCollectionLoops(template, filePath, pageNo, pagination) {
const { route, pageSize, iteratorKey } = pagination || {}; // Destructure pagination details

// Regular expression to capture the ForEach sections
const loopRegex = /<ForEach\s+([^>]+)>([\s\S]*?)<\/ForEach>/g;

let match;
// Define regex to match <paginator> tag and its content
// const paginatorRegex = /<paginator>([\s\S]*?)<\/paginator>/;
const paginatorRegex = /(?<!<!--\s*)<paginator>([\s\S]*?)<\/paginator>(?!\s*-->)/;

// Extract paginator inner content
const extractedContentMatch = template.match(paginatorRegex);
let extractedPaginator = extractedContentMatch ? extractedContentMatch[1].trim() : null;

// Remove <paginator> tag and its content from the template
template = template.replace(paginatorRegex, '').trim();

while ((match = loopRegex.exec(template)) !== null) {
const attributeString = match[1];
const loopBody = match[2];

const attributes = this.forEachAttributesAndValues('<ForEach ' + attributeString + '>');
const attributeString = match[1]; // Extract attributes string
const loopBody = match[2]; // Extract loop body content

// Extract the collection name from the attributes
//const collectionNameMatch = /collection="([^"]+)"/.exec(attributeString);
const attributes = this.forEachAttributesAndValues('<ForEach ' + attributeString + '>');

// Continue if the collection name is not found in attributes
if (!attributes.collection) {
continue; // Skip if collection name is not found
continue;
}


// Load the corresponding JSON file
// Load JSON data from specified file
let jsonData = JSON.parse(fs.readFileSync(path.join(currentDirectory, '/collections/', `${attributes.collection}.json`), 'utf8'));

// Determine the loop keyword, default to collection name
let loopKeyword = attributes.as || attributes.collection.replace(/\//g, '.');

let loopKeyword = attributes.collection.replace(/\//g, '.');
if (attributes.as) {
loopKeyword = attributes.as;
}
// Target the specific ForEach loop based on iteratorKey
const targetForEach = attributes.iteratorKey && attributes.iteratorKey === iteratorKey;

let count = attributes.count || null; // Maximum items to process, if specified

let count = null;
if(attributes.count){
count = attributes.count;
}
jsonData = this.handleOrderBy(jsonData, attributes); // Apply sorting to data

let paginationHtml = ""
const generatePgn = targetForEach && pageSize != null && pageNo != null

// slice the jsonData and generate paginator content
if (generatePgn) {
const { pageData, isFirstPage, isLastPage } = this.paginateData(jsonData, pageSize, pageNo); //slice
jsonData = pageData;

jsonData = this.handleOrderBy(jsonData, attributes);
// Generate pagination links
// const { prevLink, nextLink } = this.generatePaginationLinks(isFirstPage, isLastPage, pageNo, '/posts/');
routeMatch = route.endsWith('/') ? route : route + '/'

const { prevLink, nextLink } = this.generatePaginationLinks(isFirstPage, isLastPage, pageNo, routeMatch);

if (extractedPaginator != null
&& extractedPaginator.includes("prev")
&& extractedPaginator.includes("next") ){

paginationHtml = extractedPaginator.replace("prev", prevLink).replace("next", nextLink);
}

if (isLastPage) {
paginationHtml += "<div id='last-page-marker'></div>"; // Add a marker for the last page
}

}
let loopResult = '';
let loop = 1;
for (const item of jsonData) {
let processedBody = loopBody;
const data = { ...item, loop };
const data = { ...item, loop }; // Merge item data with loop index

// Process conditions
// Process conditions and placeholders
processedBody = this.processConditions(processedBody, data, loopKeyword, loop);

for (const key in item) {
// Regular expression to replace the placeholders
const placeholderRegex = new RegExp(`{${loopKeyword}.${key}}`, 'g');
let itemValue = item[key];
if (Array.isArray(item[key])) {
itemValue = item[key].join("|");
}
let itemValue = Array.isArray(item[key]) ? item[key].join("|") : item[key];
processedBody = processedBody.replace(placeholderRegex, itemValue);
}

loopResult += processedBody;
loop++;

if((loop-1) == count){
break;
if ((loop - 1) == count) {
break; // Stop processing if count limit is reached
}
}


// add the paginationHtml
if ( generatePgn ){
loopResult += paginationHtml
}
template = template.replace(match[0], loopResult);


}

return template;
},
paginateData(jsonData, pageSize, pageNo) {
const totalPages = Math.ceil(jsonData.length / pageSize); // Calculate total pages
const startIndex = pageNo * pageSize; // Compute start index for slicing
const endIndex = startIndex + pageSize; // Compute end index for slicing
const pageData = jsonData.slice(startIndex, endIndex); // Extract data for the current page
const isLastPage = pageNo === totalPages - 1; // Check if it is the last page
const isFirstPage = pageNo === 0; // Check if it is the first page

return { pageData, isFirstPage, isLastPage, totalPages };
},
generatePaginationLinks(isFirstPage, isLastPage, pageNo, baseUrl) {
let prevLink = isFirstPage ? `style='visibility: hidden;'` : `href='${baseUrl}${pageNo - 1}'`; // Generate previous page link
let nextLink = isLastPage ? `style='visibility: hidden;'` : `href='${baseUrl}${pageNo + 1}'`; // Generate next page link
return { prevLink, nextLink };
},

processConditions(content, data, parentCollection) {
// Regular expression to capture the If sections
Expand Down Expand Up @@ -555,10 +607,16 @@ module.exports = {
const orderBy = attributes.orderBy.split(',').map(item => item.trim());
const valueA = a[orderBy[0]];
const valueB = b[orderBy[0]];
let direction = 'asc';


//let direction = 'asc';
// Check if the orderBy array has more than one element
if (orderBy.length > 1) {
// If there is more than one element, assume the second element specifies the sort direction
// Convert the direction to lower case and trim any whitespace
direction = orderBy[1].toLowerCase().trim();
}else{
// Check if orderSort is 'desc' and set direction to 'desc' if true, otherwise set to 'asc' as default
direction = attributes.orderSort == 'desc' ? 'desc' : 'asc'
}

if (typeof valueA === 'string' && typeof valueB === 'string') {
Expand Down