I want to create a table of contents from a markdown file in next.js and I want it to be in markdown format as well.
To achieve that I have made this function:
const getMarkdownTOC = (slug) => {
const folder = process.env.PATH_ARTICULOS
const file = `${folder}${slug}.md`
const content = fs.readFileSync(file, 'utf8')
const matterResult = matter(content)
let prevLevel = 99
let spaces = ''
let toc = ''
for (const [, value] of Object.entries(compiler(matterResult.content).props.children)) {
if (value.type !== undefined && value.type.startsWith('h') && !isNaN(value.type[1])) {
const level = Number(value.type[1])
const title = value.props.children[0]
const id = value.props.id
if (level > prevLevel) {
spaces += ' '
} else {
if (level < prevLevel) {
spaces = spaces.slice(0, spaces.length - 2 * (prevLevel - level))
}
}
toc += `${spaces}* [${title}](#${id})\n`
prevLevel = level
}
}
And the function does actually work, it returns a table of contents in markdown format that can be rendered wherever I want.
However, I think there has to be some easier way to achieve that, but I've been searching and haven't found anything. There is a plugin called remark-toc which is almost what I want, but it seems to create the table of contents at the beginning of the document and I want to keep it apart.
Any ideas? Thank you!
I'm trying to call the game.LeftOrRightRow function from another file, main.js, yet it isn't working. I'd like to export the object game instead of just the particular function.
I know the function itself works because when it was in the same file it did exactly what it was meant to do (return newRow). I'm also pretty sure that I've specified the right directory.
It's my first time writing Javascript so this may come down to a silly error.
The 2048.js file who's function I want to export:
const game = Object.create(null);
game.LeftOrRightRow = function (i, direction){
let totalOne = squares[i].innerHTML;
let totalTwo = squares[i+1].innerHTML;
let totalThree = squares[i+2].innerHTML;
let totalFour = squares[i+3].innerHTML;
//the above stores the inner value of each square
//on that row, starting from the left
let row = [parseInt(totalOne), parseInt(totalTwo), parseInt(totalThree), parseInt(totalFour)]
//create an array storing the entire row
let filterRow = row.filter(num => num)
//filter out numbers from rows and store the numbers
//as a new array.
let missing = 4 - filterRow.length
//checking how many 0s there are now in the row
let zeros = Array(missing).fill(0)
//creating a new array filled with these 0s
if(direction == "right"){
let newRow = zeros.concat(filterRow)
//If we are wanting to swipe right, the zeros
//will be placed before the twos in our array.
return newRow
}else if (direction == "left"){
let newRow = filterRow.concat(zeros)
//If we are wanting to swipe left, the twos
//will be placed before the zeros in our array.
return newRow
}
}
export default Object.freeze(game);
The main.js file where I want to call the function:
import R from "../common/ramda.js";
import Json_rpc from "./Json_rpc.js";
import game from "../common/2048.js";
document.addEventListener("DOMContentLoaded", () => {
...
function moveLeftOrRight(direction){
for(let i=0; i <16; i++){
//loops over all of grid
if(i%4 === 0){
//define rows using modulus. If i MOD 4 = 0,
//this means that the square is the start of each row.
let newRow = game.LeftOrRightRow(i, direction)
//newRow = game.swipeRightRow(i)
//document.write(newRow)
squares[i].innerHTML = newRow[0]
squares[i+1].innerHTML = newRow[1]
squares[i+2].innerHTML = newRow[2]
squares[i+3].innerHTML = newRow[3]
//replaces each value with the new array
}
}
}
}
I have a set of template files present in folder say /root/path/templates/ and corresponding input files at path say /root/path/inputs/<template_name>/. Here <template_name> indicates the folder name which holds all the input files for a given template.
Let's say template file contains v
List item
alue as %1 %2 which are like place holders, where we need to replace the %n with the line number from input file line which are present in /root/path/inputs/<template_name>/.
For example:
template file name as `t1.template` and its content are `%1 %2`
Input files are `in1.input` and `in2.input` at path `/root/path/inputs/t1`.
The content for in1.input as:
First
Second
The content for in2.input is
Third
Fourth
My expected output should be like this:
t1/in1.output
First Second
t1/in2.output
Third Fourth
Now the template file can have any characters say:
`This is a template file with %1 %2 fields. Also it has %%1 percenage`
Here the corresponding output should be :
t1/in1.output
This is a template file with First Second fields. Also it has %1 percenage
t1/in2.output
This is a template file with Third Fourth fields. Also it has %1 percenage
means only %1 and %2 placeholders should be replaced. If %%n then while generating result show it as %n, it is not treated as placeholder.
Now I am trying to implement this using JS and Node:
var fs = require('fs');
var arr = fs.readdirSync("/root/path/templates");
var response = "";
for(let i=0; i<arr.length; i++) {
var item = arr[i];
var hastemplate = item.indexOf(".template");
if(hastemplate > -1) {
var tempName = item.substring(0, hastemplate);
var lineReader = require("readline").createInterface({
input: fs.createReadStream("/root/path/inputs/"+tempName)
});
lineReader.on("line", function(line) {
console.log(line + ' ' +tempName);
});
}
}
Input to this program:
/root/path/templates/t1.template
/root/path/inputs/t1/in1.input
/root/path/inputs/t1/in2.input
/root/path/templates/t2.template
When I try to print the file name in lineReader I am getting it as t2.template, i am not able to properly read the data from t1.template and its files in1.input and in2.input.
I also want to know how to read the input files and maintain the order for the output.
So got stuck here with my code incomplete.
As #Felix Kling stated in the comments, it is a scope problem and it is triggered by the fact that lineReader.on("line", ...) is not synchronous.
Here is an example illustrating your bug in a shorter manner:
for (let i = 0; i < 3; i++) {
var temp = i;
setTimeout(() => console.log(i, temp), 1000);
}
Output:
0 2
1 2
2 2
The first time var temp is encountered, the variable temp is created, then every time the loop runs, the original variable is modified. As the loop ends before any of the setTimeout calls, all of them will "know" that temp = 2.
Using let or const would bind the variable to the context of each iteration, a new variable is created each time. I highly suggest using const to leverage some advantages of immutability too, when it is the case.
const fs = require('fs');
const arr = fs.readdirSync("/root/path/templates");
let response = "";
for(let i=0; i<arr.length; i++) {
const item = arr[i];
const hastemplate = item.indexOf(".template");
if(hastemplate > -1) {
const tempName = item.substring(0, hastemplate);
const lineReader = require("readline").createInterface({
input: fs.createReadStream("/root/path/inputs/"+tempName)
});
lineReader.on("line", function(line) {
console.log(line + ' ' +tempName);
});
}
}
In this program I am unable to search for items in an array. The db variable is already defined. I am unable to get into the search function and run it. Just curious why this might be happening. I am able to run the first validate function but then stops and will not perform the rest of the code.
// Create privatized scope using a self-executing function
(function() {
console.log("hello");
// Variable initialization (DO NOT FIX ANY OF THE BELOW VAR's)
var resultsDIV = document.getElementById("results"),
searchInput = document.forms[0].search,
currentSearch = '';
// Validates search query
var validate = function (query) {
console.log("validate");
// Trim whitespace from start and end of search query
query = query.trim();
// Check search length, must have 3 characters
if (query.length < 3) {
alert("Your search query is too small, try again.");
}else{
search(query);
// (DO NOT FIX THE LINE DIRECTLY BELOW)
searchInput.focus();
}
console.log("test");
};
console.log("outside search function");
// Finds search matches
var search = function (query) {
console.log("In search function");
// split the user's search query string into an array
var queryArray = query.split(" ");
// array to store matched results from database.js
var results = [];
// loop through each index of db array
for (var i = 0, j = db.length; i < j; i++) {
console.log(i);
// each db[i] is a single video item, each title ends with a pipe "|"
// save a lowercase variable of the video title
var dbTitleEnd = db[i].indexOf('|');
var dbItems = db[i].toLowerCase().substring(0, dbTitleEnd);
}
// loop through the user's search query words
// save a lowercase variable of the search keyword
for (var ii = 0, jj = queryArray.length; ii < jj; ii++) {
var qItem = queryArray[ii].toLowerCase();
}
// is the keyword anywhere in the video title?
// If a match is found, push full db[i] into results array
var compare = dbItems.indexOf(qItem);
if (compare !== -1) {
results = results.push(db[i]);
}
results.sort();
// Check that matches were found, and run output functions
if (results.length === 0) {
noMatch();
} else {
showMatches(results);
}
};
// Put "No Results" message into page (DO NOT FIX THE HTML VAR NOR THE innerHTML)
var noMatch = function() {
var html = '' +
'<p>No Results found.</p>' +
'<p style="font-size:10px;">Try searching for "JavaScript". Just an idea.</p>'
;
resultsDIV.innerHTML = html;
};
// Put matches into page as paragraphs with anchors
var showMatches = function (results) {
// THE NEXT 4 LINES ARE CORRECT.
var html = '<p>Results</p>',
title,
url
;
// loop through all the results search() function
for (var i = 0, j = results.length; i < j; i++) {
// title of video ends with pipe
// pull the title's string using index numbers
var titleEnd = results[i].indexOf('|');
title = results[i].subString(0, titleEnd);
// pull the video url after the title
url = results[i].substring(results[i].indexOf('|') + 1, results[i].length);
// make the video link - THE NEXT LINE IS CORRECT.
html += '<p><a href=' + url + '>' + title + '</a></p>';
resultsDIV.innerHTML = html; //THIS LINE IS CORRECT.
}
};
console.log("start of program");
/***** start of program *******/
// The onsubmit event will be reviewed in upcoming Course Material.
// THE LINE DIRECTLY BELOW IS CORRECT
document.forms[0].onsubmit = function(){
var query = searchInput.value;
validate(query);
// return false is needed for most events - this will be reviewed in upcoming course material
// THE LINE DIRECTLY BELOW IS CORRECT
return false;
};
})();
I checked your code and everything is there but somehow all wrongly connected. It seems that you have completely wrong concept about for loops. I did several minor changes:
query is trimmed on reading because we need it not just in validator
validator:
if query is too short set focus back
it validated but also allowed operation to continue in case of error - changed
changed to isValid() and checked in onsubmit handler
search:
wrong concept of for loops
you retrieve dbTitleEnd/dbItems and than overwrite them by the next one
there is no need to do results = results.push(db[i]); but just `results.push(db[i]);
subString() corrected to substring()
console.log() messages are left in
See Example in jsFiddle.
I've been using the Google-provided script to generate and email CSVs from Google Sheets. I customised it so that it doesn't ask for a range or a file name. This way it just automatically emails me, periodically, with the contents of my range.
The problem is that the CSV contains empty rows, at the end, that I'd like the script to automatically filter out (which I can't achieve, because I just don't have the knowledge). The reason for this, in turn, is that the range contains empty rows - but there's a good reason for that, which is that the rows in the range sometimes expand, sometimes contract, depending on the underlying data. (The range in fact relates to a pivot table).
As a bonus prize, I'd also really like it to skip rows, if there is a single zero in either of the two columns in the rows. (I ought to be able to filter this out in the pivot table; I can, but then the filters don't work properly if new values appear).
This is an example of how my emailed CSVs are looking at the moment:
0,0
,0
0.65,0
0.75,16900
0.78,2000
0.79,500
0.8,110800
0.83,1200
0.85,20000
0.87,4500
0.9,3500
1,5000
1.1,4000
1.2,41500
,
,
,
,
,
,
,
,
,
,
,
,
,
,
This is an example of how I would like to receive that CSV:
0.75,16900
0.78,2000
0.79,500
0.8,110800
0.83,1200
0.85,20000
0.87,4500
0.9,3500
1,5000
1.1,4000
1.2,41500
Any help with this would be HUGELY appreciated. Thanks.
Here's the script I'm using currently:
var settings = {
"recipients": "myemailaddress",
"emailSubject": "CSV file",
"emailMessage": "Your CSV file is attached",
"fileExtension": ".csv",
"carriageReturn": "\r\n"
};
function onOpen() {
var subMenus = [];
subMenus.push({name: "Email Named Range as CSV", functionName: "run"});
SpreadsheetApp.getActiveSpreadsheet().addMenu("CSV", subMenus);
}
function run() {
/var namedRange = Browser.inputBox("Enter named range to convert to CSV (e.g. sampleDataRange):");/
var namedRange = "FORCSVEXPORT";
var fileName = "EPCSELLOFFERS.CSV";
if (namedRange.length !== 0 && fileName.length !== 0) {
settings.dataRangeName = namedRange;
settings.csvFileName = fileName + settings.fileExtension;
var csvFile = convertNamedRangeToCsvFile_(settings.dataRangeName, settings.csvFileName);
emailCSV_(csvFile);
}
else {
Browser.msgBox("Error: Please enter a named range and a CSV file name.");
}
}
function emailCSV_(csvFile) {
MailApp.sendEmail(settings.recipients, settings.emailSubject, settings.emailMessage, {attachments: csvFile});
}
function convertNamedRangeToCsvFile_(rngName, csvFileName) {
var ws = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(rngName);
try {
var data = ws.getValues();
var csvFile = undefined;
if (data.length > 1) {
var csv = "";
for (var row = 0; row < data.length; row += 1) {
for (var col = 0; col < data[row].length; col += 1) {
if (data[row][col].toString().indexOf(",") != -1) {
data[row][col] = "\"" + data[row][col] + "\"";
}
}
// Join each rows columns
// Add carriage return to end of each row
csv += data[row].join(",") + settings.carriageReturn;
}
csvFile = [{fileName: csvFileName, content: csv}];
}
return csvFile;
}
catch(err) {
Logger.log(err);
Browser.msgBox(err);
}
}
Rather than hack the original convertNamedRangeToCsvFile_(), I propose adding an additional step to your run(), that will call a new function to remove the unwanted rows from the csv file. Here it is:
/**
* Remove unwanted lines from given csvFile
*/
function minimizeCsvFile( csvFile ) {
// take apart the csv file contents, into an array of rows
var rows = csvFile[0].content.replace(/\r\n/g, "\n").split("\n");
var newRows = [];
for (var i = 0; i < rows.length; i++ ) {
if (rows[i] == "") continue; // skip blanks
if (rows[i] == ",") continue; // skip null values
// skip rows with either numeric value == 0
var vals = rows[i].split(",");
if (parseFloat(vals[0]) == 0.0 || parseFloat(vals[1]) == 0.0) continue;
// If we got here, we have a keeper - add it to newRows
newRows.push(rows[i]);
}
debugger; // pause to observe in debugger
var csv = newRows.join(settings.carriageReturn);
// Return a single element array with an object, exactly like
// the one from convertNamedRangeToCsvFile_.
return [{fileName: csvFile[0].fileName, content: csv}];
}
To make use of it, change the emailCSV_() line in run() to:
var minimizedCsvFile = minimizeCsvFile(csvFile);
emailCSV_(minimizedCsvFile);
And as for this...
As a bonus prize... Any help with this would be HUGELY appreciated. Thanks.
All we ever want here is for new members of StackOverflow to acknowledge when they receive help! Have a look at this answer for tips on how to accept answers. (You can build rep by asking, answering and accepting answers!)
But I've always wanted a Ferrari...