Commit 5d6467d7 authored by Holger H's avatar Holger H
Browse files

embed images into projectmeta as data-url

No related merge requests found
Pipeline #1665 passed with stages
in 7 minutes and 49 seconds
Showing with 107 additions and 42 deletions
+107 -42
......@@ -2,7 +2,6 @@ import * as path from 'path'
import { promises as fs } from 'fs'
import { existsSync } from 'fs'
import { resolveProjectPath } from './utils.mjs'
import { processMarkdown } from './projectmeta.mjs'
const REPOSITORIES_PATH = process.env.REPOSITORIES_PATH
......@@ -18,39 +17,41 @@ export const get_readme = async (req, res) => {
return
}
let filename = false
const projectPath = path.join(REPOSITORIES_PATH, 'projects', projectId)
names.forEach(name => {
extensions.forEach(extension => {
const tryPath = path.join(REPOSITORIES_PATH, 'projects', projectId, name + '.' + extension)
console.log('get_readme, trying', name + '.' + extension,)
const tryPath = path.join(projectPath, name + '.' + extension)
//console.log('get_readme, trying', name + '.' + extension)
if (existsSync(tryPath)) {
filename = tryPath
return
}
})
})
if (!filename) {
res.status(404)
res.send('readme not found')
return
}
fs.readFile(filename)
.then(file => file.toString())
.then(async str => {
const html = await processMarkdown(str)
if (html) {
res.status(200)
res.contentType('text/html')
res.send(html)
return
} else {
res.status(500)
res.send('error processing markdown')
}
})
.catch(e => {
console.error('get_readme error', e)
res.status(500)
res.send('error reading readme file')
try {
const mdStr = await fs.readFile(filename, 'utf8')
const html = await processMarkdown(mdStr, path.dirname(filename))
if (html) {
res.status(200)
res.contentType('text/html')
res.send(html)
return
})
} else {
res.status(500)
res.send('error processing markdown')
}
} catch (err) {
console.error('get_readme error', err)
res.status(500)
res.send('error reading readme file')
return
}
}
import remark from 'remark'
import remarkHtml from 'remark-html'
import { promises as fs } from 'fs'
import path from 'path'
import { visit } from 'unist-util-visit'
const markdownProcessor = remark()
.use(remarkHtml)
// Check if a path is within the allowed directory
const isPathWithinDir = (dir, filepath) => {
const relative = path.relative(dir, filepath)
return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
}
export const processMarkdown = async str => {
const ret = await markdownProcessor.process(str)
.then(resp => {
let html = resp.contents
html = html.replace(/src="\.?\/?public\//, 'src="')
return html
})
.catch(e => {
console.log('markdown process error', e)
return false
const imageToBase64 = async (imagePath, basePath) => {
try {
// Security check: Only allow images from within the project directory
if (!isPathWithinDir(basePath, imagePath)) {
console.error('Security: Attempted to load image from outside project directory:', imagePath)
return null
}
const imageBuffer = await fs.readFile(imagePath)
const base64 = imageBuffer.toString('base64')
const ext = path.extname(imagePath).substring(1).toLowerCase()
return `data:image/${ext};base64,${base64}`
} catch (err) {
console.error('Error converting image to base64:', imagePath, err)
return null
}
}
// Custom remark plugin to handle images
function remarkEmbedImages(basePath) {
return async function transformer(tree) {
const promises = []
// First collect all image nodes
const imageNodes = []
visit(tree, 'image', (node) => {
imageNodes.push(node)
})
return ret
// Then process them all in parallel
await Promise.all(imageNodes.map(async (node) => {
try {
const absoluteImagePath = path.resolve(basePath, node.url)
const base64Data = await imageToBase64(absoluteImagePath, basePath)
if (base64Data) {
node.url = base64Data
}
} catch (err) {
console.error('Error processing image:', err)
}
}))
}
}
// Create a unified processor that handles both markdown and HTML
const createProcessor = (basePath) => {
return remark()
.use(remarkEmbedImages, basePath)
.use(remarkHtml, { sanitize: false }) // Allow raw HTML
}
export const processMarkdown = async (str, basePath) => {
try {
const processor = createProcessor(basePath)
const file = await processor.process(str)
let html = String(file)
// Clean up any remaining relative paths
html = html.replace(/src="\.?\/?public\//, 'src="')
return html
} catch (e) {
console.error('Markdown process error:', e)
return false
}
}
......@@ -6,15 +6,16 @@ import { promises as fs } from "fs";
import { processMarkdown } from "../projectmeta.mjs";
import interkit_server from "../interkit_server.mjs";
import { getProjectPath } from "../filesystem.mjs";
import path from "path";
const watchedFileReMd = /^(project|readme|description)\.(md|markdown)$/i;
const updateFileMd = (projectId, { path, filename, basename, extension }) => {
//console.log("updateFileMd", { path, filename, basename, extension });
fs.readFile(path)
const updateFileMd = (projectId, { path: filePath, filename, basename, extension }) => {
//console.log("updateFileMd", { path: filePath, filename, basename, extension });
fs.readFile(filePath)
.then((file) => file.toString())
.then(async (mdStr) => {
const html = await processMarkdown(mdStr);
const html = await processMarkdown(mdStr, path.dirname(filePath));
interkit_server.call("project.updateUiState", {
projectId: projectId,
section: "metafile." + basename.toLowerCase(),
......@@ -27,7 +28,7 @@ const updateFileMd = (projectId, { path, filename, basename, extension }) => {
.catch((e) => {
console.error(
"updateFileMd error",
{ path, filename, basename, extension },
{ path: filePath, filename, basename, extension },
e
);
});
......@@ -39,8 +40,8 @@ const updateProjectMdFiles = async (projectId, files) =>
let matchMd = file.match(watchedFileReMd);
if (matchMd) {
const [filename, basename, extension] = matchMd;
const path = projectPath + "/" + filename;
updateFileMd(projectId, { path, filename, basename, extension });
const filePath = path.join(projectPath, filename);
updateFileMd(projectId, { path: filePath, filename, basename, extension });
}
});
......
......@@ -146,4 +146,8 @@
:global(.markdownContent li) {
margin-bottom: 1em;
}
:global(.markdownContent img) {
max-width:100%;
}
</style>
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment