Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

markdown-notes-tree

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

markdown-notes-tree - npm Package Compare versions

Comparing version 1.10.2 to 1.11.0-beta.0

src/markdown-parser.js

5

package.json
{
"name": "markdown-notes-tree",
"version": "1.10.2",
"version": "1.11.0-beta.0",
"description": "Generate Markdown trees that act as a table of contents for a folder structure with Markdown notes",

@@ -36,3 +36,3 @@ "main": "src/index.js",

"fs-extra": "^8.1.0",
"jest": "^26.1.0",
"jest": "^26.6.3",
"prettier": "^1.19.1",

@@ -43,2 +43,3 @@ "prettier-check": "^2.0.0"

"front-matter": "^3.1.0",
"mdast-util-from-markdown": "^0.8.5",
"minimatch": "^3.0.4",

@@ -45,0 +46,0 @@ "minimist": "^1.2.5"

@@ -114,2 +114,8 @@ # markdown-notes-tree

## Known limitations
The tool does not support Markdown links inside the titles of notes and subdirectory README files. This is intentional, because these titles will be turned into links by the tool and Markdown does not support nested links.
As a workaround, HTML links can be used ([example](test-data/subdirectory-title-rich-text/expected/sub2/sub2b/README.md)).
## Development

@@ -116,0 +122,0 @@

150

src/file-contents.js

@@ -5,2 +5,4 @@ "use strict";

const markdownParser = require("./markdown-parser");
module.exports = {

@@ -37,13 +39,24 @@ getTitleFromMarkdownContents,

const contentsWithoutFrontMatter = parsedFrontMatter.body.trimLeft();
const lines = contentsWithoutFrontMatter.split(/\r\n|\r|\n/);
const astNode = markdownParser.getAstNodeFromContents(contentsWithoutFrontMatter);
const titleNode = markdownParser.getFirstLevel1HeadingChild(astNode);
for (const line of lines) {
if (line.startsWith("# ")) {
return line.substring(2);
} else if (line !== "" && line !== markers.directoryReadmeStart) {
return undefined;
}
if (!titleNode) {
return undefined;
}
return undefined;
if (markdownParser.hasLinkDescendant(titleNode)) {
// links are the only content that can be used inside headings but not inside links
throw new Error(
"Title cannot contain Markdown links since this would mess up the links in the tree (consider using HTML as a workaround)"
);
}
const contentStartIndex = markdownParser.getContentStartIndex(titleNode);
const contentEndIndex = markdownParser.getContentEndIndex(titleNode);
if (contentStartIndex === contentEndIndex) {
return undefined;
}
return contentsWithoutFrontMatter.substring(contentStartIndex, contentEndIndex);
}

@@ -53,30 +66,27 @@

currentContents = normalizeContents(currentContents);
const astNode = markdownParser.getAstNodeFromContents(currentContents);
const indexTreeStartMarker = currentContents.indexOf(markers.mainReadmeTreeStart);
const treeStartMarkerPresent = indexTreeStartMarker >= 0;
let contentsBeforeTree;
const treeStartMarkerNode = markdownParser.getFirstHtmlChildWithValue(
markers.mainReadmeTreeStart,
astNode
);
if (treeStartMarkerPresent) {
contentsBeforeTree = currentContents.substring(0, indexTreeStartMarker);
} else {
contentsBeforeTree = currentContents + environment.endOfLine.repeat(2);
}
const treeEndMarkerNode = markdownParser.getFirstHtmlChildWithValue(
markers.mainReadmeTreeEnd,
astNode
);
const indexTreeEndMarker = currentContents.indexOf(markers.mainReadmeTreeEnd);
const treeEndMarkerPresent = indexTreeEndMarker >= 0;
let contentsAfterTree;
const contentsBeforeTree = getMainReadmeContentsBeforeTree(
currentContents,
treeStartMarkerNode,
environment
);
const treeEndMarkerValid =
treeEndMarkerPresent && treeStartMarkerPresent && indexTreeEndMarker > indexTreeStartMarker;
const contentsAfterTree = getMainReadmeContentsAfterTree(
currentContents,
treeStartMarkerNode,
treeEndMarkerNode,
environment
);
if (treeEndMarkerValid) {
contentsAfterTree = currentContents.substring(
indexTreeEndMarker + markers.mainReadmeTreeEnd.length
);
} else if (treeEndMarkerPresent) {
throw new Error("Invalid file structure: tree end marker found before tree start marker");
} else {
contentsAfterTree = environment.endOfLine;
}
return (

@@ -93,2 +103,34 @@ contentsBeforeTree +

function getMainReadmeContentsBeforeTree(contents, treeStartMarkerNode, environment) {
if (!treeStartMarkerNode) {
return contents + environment.endOfLine.repeat(2);
}
const indexTreeStartMarker = markdownParser.getStartIndex(treeStartMarkerNode);
return contents.substring(0, indexTreeStartMarker);
}
function getMainReadmeContentsAfterTree(
contents,
treeStartMarkerNode,
treeEndMarkerNode,
environment
) {
if (!treeEndMarkerNode) {
return environment.endOfLine;
}
const treeEndMarkerValid =
treeStartMarkerNode &&
markdownParser.getStartIndex(treeEndMarkerNode) >
markdownParser.getStartIndex(treeStartMarkerNode);
if (!treeEndMarkerValid) {
throw new Error("Invalid file structure: tree end marker found before tree start marker");
}
const indexEndOfTreeEndMarker = markdownParser.getEndIndex(treeEndMarkerNode);
return contents.substring(indexEndOfTreeEndMarker);
}
function normalizeContents(contents) {

@@ -98,15 +140,8 @@ return contents

.replace(markers.mainReadmeTreeEnd_v_1_8_0, markers.mainReadmeTreeEnd)
.replace(markers.directoryReadmeStart_v_1_8_0, markers.directoryReadmeStart)
.trimLeft();
.replace(markers.directoryReadmeStart_v_1_8_0, markers.directoryReadmeStart);
}
function getNewDirectoryReadmeContents(name, currentContents, markdownForTree, environment) {
currentContents = normalizeContents(currentContents);
function getNewDirectoryReadmeContents(title, description, markdownForTree, environment) {
const titleHeading = `# ${title}`;
const currentTitle = getTitleFromMarkdownContents(currentContents);
const title = currentTitle || name;
const titleLine = `# ${title}`;
const description = getDirectoryDescriptionFromCurrentContents(currentContents);
let partBetweenDescriptionMarkers = environment.endOfLine.repeat(2);

@@ -122,3 +157,3 @@

environment.endOfLine.repeat(2) +
titleLine +
titleHeading +
environment.endOfLine.repeat(2) +

@@ -135,23 +170,32 @@ markers.directoryReadmeDescriptionStart +

function getDirectoryDescriptionFromCurrentContents(currentContents) {
const indexStartMarker = currentContents.indexOf(markers.directoryReadmeDescriptionStart);
const indexEndMarker = currentContents.indexOf(markers.directoryReadmeDescriptionEnd);
const astNode = markdownParser.getAstNodeFromContents(currentContents);
const startMarkerPresent = indexStartMarker >= 0;
const endMarkerPresent = indexEndMarker >= 0;
const startMarkerNode = markdownParser.getFirstHtmlChildWithValue(
markers.directoryReadmeDescriptionStart,
astNode
);
const endMarkerNode = markdownParser.getFirstHtmlChildWithValue(
markers.directoryReadmeDescriptionEnd,
astNode
);
if (!startMarkerNode && !endMarkerNode) {
return "";
}
const markersValid =
startMarkerPresent && endMarkerPresent && indexEndMarker > indexStartMarker;
startMarkerNode &&
endMarkerNode &&
markdownParser.getStartIndex(endMarkerNode) > markdownParser.getStartIndex(startMarkerNode);
if (markersValid) {
const descriptionStart = indexStartMarker + markers.directoryReadmeDescriptionStart.length;
const descriptionEnd = indexEndMarker;
const descriptionStart = markdownParser.getEndIndex(startMarkerNode);
const descriptionEnd = markdownParser.getStartIndex(endMarkerNode);
return currentContents.substring(descriptionStart, descriptionEnd).trim();
} else if (startMarkerPresent || endMarkerPresent) {
} else {
throw new Error(
"Invalid file structure: only one description marker found or end marker found before start marker"
);
} else {
return "";
}
}

@@ -11,2 +11,12 @@ "use strict";

describe("getTitleFromMarkdownContents", () => {
test("it should handle empty files", () => {
const contents = "";
expect(fileContents.getTitleFromMarkdownContents(contents)).toBeUndefined();
});
test("it should handle empty title", () => {
const contents = "# ";
expect(fileContents.getTitleFromMarkdownContents(contents)).toBeUndefined();
});
test("it should support CRLF line endings", () => {

@@ -33,3 +43,8 @@ const contents = "# test" + "\r\n" + "second line";

test("it should ignore the directory README start marker", () => {
const contents = "<!-- generated by markdown-notes-tree -->" + "\n" + "\n" + "# test";
const contents = dedent(
`<!-- generated by markdown-notes-tree -->
# test`
);
expect(fileContents.getTitleFromMarkdownContents(contents)).toBe("test");

@@ -39,3 +54,8 @@ });

test("it should ignore the old directory README start marker", () => {
const contents = "<!-- this entire file is auto-generated -->" + "\n" + "\n" + "# test";
const contents = dedent(
`<!-- this entire file is auto-generated -->
# test`
);
expect(fileContents.getTitleFromMarkdownContents(contents)).toBe("test");

@@ -45,13 +65,10 @@ });

test("it should return the tree title from YAML front matter if available", () => {
const contents =
"---" +
"\n" +
"tree_title: TreeTitle" +
"\n" +
"---" +
"\n" +
"\n" +
"# test" +
"\n" +
"second line";
const contents = dedent(
`---
tree_title: TreeTitle
---
# test
second line`
);

@@ -62,13 +79,10 @@ expect(fileContents.getTitleFromMarkdownContents(contents)).toBe("TreeTitle");

test("it should ignore YAML front matter that has no tree_title attribute", () => {
const contents =
"---" +
"\n" +
"description: Some text" +
"\n" +
"---" +
"\n" +
"\n" +
"# test" +
"\n" +
"second line";
const contents = dedent(
`---
description: Some text
---
# test
second line`
);

@@ -78,6 +92,30 @@ expect(fileContents.getTitleFromMarkdownContents(contents)).toBe("test");

test("it should return undefined if the first real line doesn't have a title", () => {
test("it should return undefined if the file doesn't have a title", () => {
const contents = "some non-title content";
expect(fileContents.getTitleFromMarkdownContents(contents)).toBeUndefined();
});
test("it should support content before the title", () => {
const contents = dedent(
`content before title
# test
some other content`
);
expect(fileContents.getTitleFromMarkdownContents(contents)).toBe("test");
});
test("it should fail if title contains content that is not supported inside links", () => {
const contents = dedent(
`# test [mistermicheels](http://mistermicheels.com)
some other content`
);
expect(() => fileContents.getTitleFromMarkdownContents(contents)).toThrow(
"Title cannot contain Markdown links since this would mess up the links in the tree (consider using HTML as a workaround)"
);
});
});

@@ -228,11 +266,24 @@

});
test("it should ignore markers inside code snippets etc.", () => {
const currentContents =
dedent(`some content
\`<!-- tree generated by markdown-notes-tree ends here -->\`
markdownForTree`) + endOfLine;
expect(() =>
fileContents.getNewMainReadmeContents(currentContents, "markdownForTree", {
endOfLine
})
).not.toThrow();
});
});
describe("getNewDirectoryReadmeContents", () => {
test("it should handle empty current contents", () => {
const currentContents = "";
test("it should handle empty description", () => {
const result = fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"",
"markdownForTree",

@@ -256,13 +307,6 @@ { endOfLine }

test("it should handle current contents without description markers (as generated by older version)", () => {
const currentContents =
dedent(`<!-- generated by markdown-notes-tree -->
# name
markdownForTree`) + endOfLine;
test("it should handle non-empty description", () => {
const result = fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"This is a description.",
"markdownForTree",

@@ -279,2 +323,4 @@ { endOfLine }

This is a description.
<!-- optional markdown-notes-tree directory description ends here -->

@@ -286,4 +332,12 @@

});
});
test("it should handle current contents without description between markers", () => {
describe("getDirectoryDescriptionFromCurrentContents", () => {
test("it should handle empty current contents", () => {
const currentContents = "";
const result = fileContents.getDirectoryDescriptionFromCurrentContents(currentContents);
expect(result).toBe("");
});
test("it should handle current contents without description markers (as generated by older version)", () => {
const currentContents =

@@ -293,17 +347,11 @@ dedent(`<!-- generated by markdown-notes-tree -->

# name
<!-- optional markdown-notes-tree directory description starts here -->
<!-- optional markdown-notes-tree directory description ends here -->
markdownForTree`) + endOfLine;
const result = fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"markdownForTree",
{ endOfLine }
);
const result = fileContents.getDirectoryDescriptionFromCurrentContents(currentContents);
expect(result).toBe("");
});
const expected =
test("it should handle current contents without description between markers", () => {
const currentContents =
dedent(`<!-- generated by markdown-notes-tree -->

@@ -319,3 +367,4 @@

expect(result).toBe(expected);
const result = fileContents.getDirectoryDescriptionFromCurrentContents(currentContents);
expect(result).toBe("");
});

@@ -337,23 +386,4 @@

const result = fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"markdownForTree",
{ endOfLine }
);
const expected =
dedent(`<!-- generated by markdown-notes-tree -->
# name
<!-- optional markdown-notes-tree directory description starts here -->
This is a description.
<!-- optional markdown-notes-tree directory description ends here -->
markdownForTree`) + endOfLine;
expect(result).toBe(expected);
const result = fileContents.getDirectoryDescriptionFromCurrentContents(currentContents);
expect(result).toBe("This is a description.");
});

@@ -375,23 +405,4 @@

const result = fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"markdownForTree",
{ endOfLine }
);
const expected =
dedent(`<!-- generated by markdown-notes-tree -->
# name
<!-- optional markdown-notes-tree directory description starts here -->
This is a description.
<!-- optional markdown-notes-tree directory description ends here -->
markdownForTree`) + endOfLine;
expect(result).toBe(expected);
const result = fileContents.getDirectoryDescriptionFromCurrentContents(currentContents);
expect(result).toBe("This is a description.");
});

@@ -412,8 +423,3 @@

expect(() =>
fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"markdownForTree",
{ endOfLine }
)
fileContents.getDirectoryDescriptionFromCurrentContents(currentContents)
).toThrow(

@@ -423,36 +429,3 @@ "Invalid file structure: only one description marker found or end marker found before start marker"

});
test("it should preserve the title from the current contents if provided", () => {
const currentContents =
dedent(`<!-- generated by markdown-notes-tree -->
# Custom title goes here
<!-- optional markdown-notes-tree directory description starts here -->
<!-- optional markdown-notes-tree directory description ends here -->
markdownForTree`) + endOfLine;
const result = fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"markdownForTree",
{ endOfLine }
);
const expected =
dedent(`<!-- generated by markdown-notes-tree -->
# Custom title goes here
<!-- optional markdown-notes-tree directory description starts here -->
<!-- optional markdown-notes-tree directory description ends here -->
markdownForTree`) + endOfLine;
expect(result).toBe(expected);
});
});
});

@@ -48,6 +48,7 @@ "use strict";

const readmeContents = getCurrentContents(relativeReadmePath);
const readmeTitle = getTitleFromMarkdownFile(readmeContents, relativeReadmePath);
treeNodes.push({
isDirectory: true,
title: fileContents.getTitleFromMarkdownContents(readmeContents) || directory.name,
title: readmeTitle || directory.name,
description: getDescriptionFromDirectoryReadmeContents(

@@ -72,6 +73,7 @@ readmeContents,

const relativePath = path.join(relativeParentPath, file.name);
const contents = getCurrentContents(relativePath);
treeNodes.push({
isDirectory: false,
title: getTitleFromMarkdownFileOrThrow(relativePath),
title: getTitleFromMarkdownFileOrThrow(contents, relativePath),
filename: file.name

@@ -108,6 +110,14 @@ });

function getTitleFromMarkdownFileOrThrow(relativePath) {
const contents = getCurrentContents(relativePath);
const title = fileContents.getTitleFromMarkdownContents(contents);
function getTitleFromMarkdownFile(contents, relativePath) {
try {
return fileContents.getTitleFromMarkdownContents(contents);
} catch (error) {
const absolutePath = pathUtils.getAbsolutePath(relativePath);
throw new Error(`Cannot get title from file ${absolutePath}: ${error.message}`);
}
}
function getTitleFromMarkdownFileOrThrow(contents, relativePath) {
const title = getTitleFromMarkdownFile(contents, relativePath);
if (!title) {

@@ -114,0 +124,0 @@ const absolutePath = pathUtils.getAbsolutePath(relativePath);

@@ -37,3 +37,4 @@ "use strict";

[treeNode.filename],
treeNode.filename,
treeNode.title,
treeNode.description,
treeNode.children,

@@ -46,4 +47,4 @@ environment

function writeTreesForDirectory(pathParts, name, treeForDirectory, environment) {
writeTreeToDirectoryReadme(pathParts, name, treeForDirectory, environment);
function writeTreesForDirectory(pathParts, title, description, treeForDirectory, environment) {
writeTreeToDirectoryReadme(pathParts, title, description, treeForDirectory, environment);

@@ -54,3 +55,4 @@ for (const treeNode of treeForDirectory) {

[...pathParts, treeNode.filename],
treeNode.filename,
treeNode.title,
treeNode.description,
treeNode.children,

@@ -63,3 +65,3 @@ environment

function writeTreeToDirectoryReadme(pathParts, name, treeForDirectory, environment) {
function writeTreeToDirectoryReadme(pathParts, title, description, treeForDirectory, environment) {
const filePathParts = [...pathParts, "README.md"];

@@ -78,4 +80,4 @@ const relativeFilePath = path.join(...filePathParts);

const newContents = fileContents.getNewDirectoryReadmeContents(
name,
currentContents,
title,
description,
markdownForTree,

@@ -82,0 +84,0 @@ environment

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc