XeNote/lib/utils.js

280 lines
8.6 KiB
JavaScript
Raw Normal View History

2024-01-07 16:22:37 +00:00
import { Node } from './node'
import { Transformer } from './transformer'
import { unified } from 'unified'
import markdown from 'remark-parse'
import { toString } from 'mdast-util-to-string'
2024-01-07 16:22:37 +00:00
import path from 'path'
import fs from 'fs'
2024-01-07 18:17:57 +00:00
const dirTree = require("directory-tree");
2023-12-26 04:42:33 +00:00
class Util {
2024-01-07 16:22:37 +00:00
_counter
cachedSlugMap
2023-12-26 04:42:33 +00:00
constructor() {
2024-01-07 16:22:37 +00:00
this._counter = 0
this.cachedSlugMap = this.getSlugHashMap()
2023-12-26 04:42:33 +00:00
}
2024-01-07 16:22:37 +00:00
/**
* @returns {string | null}
* */
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
2023-12-26 04:39:48 +00:00
getContent(slug) {
2024-01-07 16:22:37 +00:00
const currentFilePath = this.toFilePath(slug)
2023-12-26 04:39:48 +00:00
if (currentFilePath === undefined || currentFilePath == null) return null
return Node.readFileSync(currentFilePath)
}
2023-12-26 04:39:48 +00:00
getShortSummary(slug) {
const content = this.getContent(slug)
2023-12-26 04:39:48 +00:00
if (content === undefined || content === null) {
return
}
2023-12-26 04:39:48 +00:00
const tree = unified().use(markdown)
.parse(content)
2024-01-07 16:22:37 +00:00
const plainText = toString(tree)
return plainText.split(' ').splice(0, 40).join(' ')
}
2020-11-30 11:29:34 +00:00
2023-12-26 04:39:48 +00:00
getAllMarkdownFiles() {
return Node.getFiles(Node.getMarkdownFolder())
}
2020-11-28 15:45:01 +00:00
2023-12-26 04:39:48 +00:00
getSinglePost(slug) {
// List of filenames that will provide existing links to wikilink
2024-01-07 16:22:37 +00:00
const currentFilePath = this.toFilePath(slug)
// console.log("currentFilePath: ", currentFilePath)
2024-01-07 16:22:37 +00:00
const fileContent = Node.readFileSync(currentFilePath)
2024-01-07 16:22:37 +00:00
// const currentFileFrontMatter = Transformer.getFrontMatterData(fileContent)
2023-12-26 04:39:48 +00:00
// console.log("===============\n\nFile is scanning: ", slug)
const [htmlContent] = Transformer.getHtmlContent(fileContent)
// console.log("==================================")
2024-01-07 16:22:37 +00:00
// console.log("hrmlcontents and backlinks")
2023-12-26 04:39:48 +00:00
return {
id: slug,
// ...currentFileFrontMatter,
2024-01-07 16:22:37 +00:00
data: htmlContent
2023-12-26 04:39:48 +00:00
}
}
2023-12-26 04:39:48 +00:00
toFilePath(slug) {
return this.cachedSlugMap[slug]
2023-12-26 04:39:48 +00:00
}
2023-12-26 04:39:48 +00:00
getSlugHashMap() {
// This is to solve problem of converting between slug and filepath,
// where previously if I convert a slug to a file path sometime
// it does not always resolve to correct filepath, converting function is not bi-directional
// and not conflict-free, other solution was considered (hash file name into a hash, but this
// is not SEO-friendly and make url look ugly ==> I chose this
const slugMap = new Map()
2024-01-07 16:22:37 +00:00
this.getAllMarkdownFiles().forEach(aFile => {
const aSlug = this.toSlug(aFile)
2023-12-26 04:39:48 +00:00
// if (slugMap.has(aSlug)) {
// slugMap[aSlug].push(aFile)
// } else {
// slugMap[aSlug] = [aFile]
// }
// Note: [Future improvement] Resolve conflict
slugMap[aSlug] = aFile
})
2024-01-07 16:22:37 +00:00
const indexFile = '/🌎 Home.md'
slugMap.index = Node.getMarkdownFolder() + indexFile
2023-12-26 04:39:48 +00:00
slugMap['/'] = Node.getMarkdownFolder() + indexFile
2023-12-26 04:39:48 +00:00
return slugMap
}
2023-12-26 04:39:48 +00:00
toSlug(filePath) {
const markdownFolder = Node.getMarkdownFolder()
const isFile = Node.isFile(filePath)
const isMarkdownFolder = filePath.includes(markdownFolder)
2024-01-07 16:22:37 +00:00
if (isFile && Boolean(isMarkdownFolder)) {
2023-12-26 04:39:48 +00:00
return filePath.replace(markdownFolder, '')
.replaceAll('/', '_')
.replaceAll(' ', '+')
.replaceAll('&', '-')
.replace('.md', '')
} else {
2024-01-07 16:22:37 +00:00
// TODO handle this properly
2023-12-26 04:39:48 +00:00
return '/'
}
}
2023-12-26 04:39:48 +00:00
constructGraphData() {
2024-01-07 16:22:37 +00:00
const filepath = path.join(process.cwd(), 'graph-data.json')
2023-12-26 04:39:48 +00:00
if (Node.isFile(filepath)) {
2024-01-07 16:22:37 +00:00
const data = fs.readFileSync(filepath)
2023-12-26 04:39:48 +00:00
return JSON.parse(String(data))
} else {
2024-01-07 16:22:37 +00:00
const filePaths = this.getAllMarkdownFiles()
2023-12-26 04:39:48 +00:00
const edges = []
const nodes = []
filePaths
.forEach(aFilePath => {
// const {currentFilePath} = getFileNames(filename)
const aNode = {
title: Transformer.parseFileNameFromPath(aFilePath),
slug: this.toSlug(aFilePath),
shortSummary: this.getShortSummary(this.toSlug(aFilePath))
}
2023-12-26 04:39:48 +00:00
nodes.push(aNode)
// console.log("Constructing graph for node: " + aFilePath )
const internalLinks = Transformer.getInternalLinks(aFilePath)
internalLinks.forEach(aLink => {
if (aLink.slug === null || aLink.slug.length === 0) return
const anEdge = {
source: this.toSlug(aFilePath),
2024-01-07 16:22:37 +00:00
target: aLink.slug
2023-12-26 04:39:48 +00:00
}
edges.push(anEdge)
// console.log("Source: " + anEdge.source)
// console.log("Target: " + anEdge.target)
})
// console.log("==============Constructing graph" )
}
)
2024-01-07 16:22:37 +00:00
const data = { nodes, edges }
fs.writeFileSync(filepath, JSON.stringify(data), 'utf-8')
return data
2023-12-26 04:39:48 +00:00
}
}
2020-11-28 15:45:01 +00:00
2023-12-26 04:39:48 +00:00
getLocalGraphData(currentNodeId) {
const { nodes, edges } = constructGraphData()
2023-12-26 04:39:48 +00:00
const newNodes = nodes.map(aNode => (
{
data: {
id: aNode.slug.toString(),
2024-01-07 16:22:37 +00:00
label: Transformer.parseFileNameFromPath(this.toFilePath(aNode.slug))
2023-12-26 04:39:48 +00:00
}
}
))
2023-12-26 04:39:48 +00:00
const newEdges = edges.map(anEdge => ({
data: {
2023-12-26 04:39:48 +00:00
source: anEdge.source,
2024-01-07 16:22:37 +00:00
target: anEdge.target
}
2023-12-26 04:39:48 +00:00
}))
2023-12-26 04:39:48 +00:00
const existingNodeIDs = newNodes.map(aNode => aNode.data.id)
currentNodeId = currentNodeId === 'index' ? '__index' : currentNodeId
2024-01-07 16:22:37 +00:00
if (currentNodeId != null && Boolean(existingNodeIDs.includes(currentNodeId))) {
2023-12-26 04:39:48 +00:00
const outGoingNodeIds = newEdges
.filter(anEdge => anEdge.data.source === currentNodeId)
.map(anEdge => anEdge.data.target)
2023-12-26 04:39:48 +00:00
const incomingNodeIds = newEdges
.filter(anEdge => anEdge.data.target === currentNodeId)
.map(anEdge => anEdge.data.source)
2023-12-26 04:39:48 +00:00
outGoingNodeIds.push(currentNodeId)
2023-12-26 04:39:48 +00:00
const localNodeIds = incomingNodeIds.concat(outGoingNodeIds.filter(item => incomingNodeIds.indexOf(item) < 0))
if (localNodeIds.indexOf(currentNodeId) < 0) {
localNodeIds.push(currentNodeId)
}
2023-12-26 04:39:48 +00:00
const localNodes = newNodes.filter(aNode => localNodeIds.includes(aNode.data.id))
2024-01-07 16:22:37 +00:00
let localEdges = newEdges.filter(edge => localNodeIds.includes(edge.data.source)).filter(edge => localNodeIds.includes(edge.data.target))
2023-12-26 04:39:48 +00:00
// Filter self-reference edges
localEdges = localEdges.filter(edge => edge.data.source !== edge.data.target)
2022-04-18 09:47:02 +00:00
2023-12-26 04:39:48 +00:00
// TODO: Find out why target ==='/' in some case
localEdges = localEdges.filter(edge => edge.data.target !== '/')
return {
nodes: localNodes,
edges: localEdges
}
} else {
const filteredEdges = newEdges
.filter(edge => existingNodeIDs.includes(edge.data.source))
.filter(edge => existingNodeIDs.includes(edge.data.target))
return {
nodes: newNodes,
edges: filteredEdges
}
}
2023-12-26 04:39:48 +00:00
}
2022-03-23 03:50:06 +00:00
2023-12-26 04:39:48 +00:00
getAllSlugs() {
2024-01-07 16:22:37 +00:00
// console.log("\n\nAll Posts are scanning")
2023-12-26 04:39:48 +00:00
// Get file names under /posts
const markdownFolder = Node.getMarkdownFolder()
const markdownFiles = Node.getFiles(markdownFolder)
2024-01-07 16:22:37 +00:00
const filePaths = markdownFiles.filter(file => !(Boolean(file.endsWith('index')) || Boolean(file.endsWith('sidebar'))))
return filePaths.map(f => this.toSlug(f))
2023-12-26 04:39:48 +00:00
}
2023-12-26 04:39:48 +00:00
getDirectoryData() {
2024-01-07 16:22:37 +00:00
const filteredDirectory = dirTree(Node.getMarkdownFolder(), { extensions: /\.md/, exclude: [/\.git/, /\.obsidian/] })
return this.convertObject(filteredDirectory)
}
2023-12-26 04:39:48 +00:00
convertObject(thisObject) {
const children = []
2024-01-07 16:22:37 +00:00
const slugs = this.getAllSlugs()
2023-12-26 04:39:48 +00:00
2024-01-07 18:17:57 +00:00
function findFunc(_this, slug) {
const fileName = Transformer.parseFileNameFromPath(_this.toFilePath(slug))
2023-12-26 04:39:48 +00:00
return Transformer.normalizeFileName(fileName) === Transformer.normalizeFileName(thisObject.name)
2024-01-07 16:22:37 +00:00
}
2024-01-07 18:17:57 +00:00
const foundSlugs = slugs.find(slug => findFunc(this, slug))
2024-01-07 16:22:37 +00:00
let routerPath = foundSlugs !== (null | undefined) ? foundSlugs : null
routerPath = routerPath !== (null | undefined) ? '/notes/' + routerPath : null
2023-12-26 04:39:48 +00:00
const newObject = {
name: thisObject.name,
2024-01-07 16:22:37 +00:00
children,
id: (this._counter++).toString(),
2024-01-07 16:22:37 +00:00
routePath: routerPath !== (null | undefined) ? routerPath : null
}
2023-12-26 04:39:48 +00:00
if (thisObject.children != null && thisObject.children.length > 0) {
thisObject.children.forEach(aChild => {
const newChild = this.convertObject(aChild)
2023-12-26 04:39:48 +00:00
children.push(newChild)
})
2024-01-07 16:22:37 +00:00
return newObject
2023-12-26 04:39:48 +00:00
} else {
return newObject
}
2023-12-26 04:39:48 +00:00
}
flat = (array) => {
2024-01-07 16:22:37 +00:00
let result = []
const outerThis = this
2024-01-07 16:22:37 +00:00
// eslint-disable-next-line @typescript-eslint/space-before-function-paren
2023-12-26 04:39:48 +00:00
array.forEach(function(a) {
2024-01-07 16:22:37 +00:00
result.push(a)
2023-12-26 04:39:48 +00:00
if (Array.isArray(a.children)) {
2024-01-07 16:22:37 +00:00
result = result.concat(outerThis.flat(a.children))
2023-12-26 04:39:48 +00:00
}
2024-01-07 16:22:37 +00:00
})
return result
2023-12-26 04:39:48 +00:00
}
2024-01-07 16:22:37 +00:00
2023-12-26 04:39:48 +00:00
getFlattenArray(thisObject) {
return this.flat(thisObject.children)
2023-12-26 04:39:48 +00:00
}
}
2023-12-26 04:39:48 +00:00
2024-01-07 16:22:37 +00:00
const util = new Util()
export default util