Skip to content

Commit

Permalink
made exporting media files in a separate media folder
Browse files Browse the repository at this point in the history
  • Loading branch information
iskaktoltay committed Jul 25, 2024
1 parent 4888b49 commit 748078a
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 44 deletions.
27 changes: 2 additions & 25 deletions frontend/apps/desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {appStore} from './app-store'
import autoUpdate from './auto-update'
import {startMainDaemon} from './daemon'
import {saveCidAsFile} from './save-cid-as-file'
import {saveMarkdownFile} from './save-markdown-file'

const OS_REGISTER_SCHEME = 'hm'

Expand Down Expand Up @@ -129,32 +130,8 @@ ipcMain.handle('dark-mode:system', () => {
})

ipcMain.on('save-file', saveCidAsFile)
ipcMain.on('export-document', async (_event, args) => {
const {title, markdown} = args
const camelTitle = title
.split(' ')
.map(
(word: string) =>
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
)
.join('')
const {filePath} = await dialog.showSaveDialog({
title: 'Save Markdown',
defaultPath: path.join(__dirname, camelTitle + '.md'),
buttonLabel: 'Save',
filters: [{name: 'Markdown Files', extensions: ['md']}],
})

if (filePath) {
fs.writeFile(filePath, markdown, (err) => {
if (err) {
console.error('Error saving file:', err)
return
}
console.log('File successfully saved:', filePath)
})
}
})
ipcMain.on('export-document', saveMarkdownFile)

ipcMain.on(
'export-multiple-documents',
Expand Down
8 changes: 6 additions & 2 deletions frontend/apps/desktop/src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,12 @@ function MainApp({
saveCidAsFile={async (cid: string, name: string) => {
ipc.send?.('save-file', {cid, name})
}}
exportDocument={async (title: string, markdown: string) => {
ipc.send?.('export-document', {title, markdown})
exportDocument={async (
title: string,
markdownContent: string,
mediaFiles: {url: string; filename: string}[],
) => {
ipc.send?.('export-document', {title, markdownContent, mediaFiles})
}}
exportDocuments={async (
documents: {
Expand Down
92 changes: 92 additions & 0 deletions frontend/apps/desktop/src/save-markdown-file.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import {API_HTTP_URL} from '@mintter/shared'
import {app, dialog, net} from 'electron'
import fs from 'fs'
import path from 'node:path'

const {debug, error} = console

export async function saveMarkdownFile(event, args) {
const {title, markdownContent, mediaFiles} = args
const formattedTitle = title
.split(' ')
.map(
(word: string) =>
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
)
.join('')

const {filePath} = await dialog.showSaveDialog({
title: 'Save Markdown and Media',
defaultPath: path.join(app.getPath('documents'), formattedTitle),
buttonLabel: 'Save',
filters: [{name: 'Markdown Files', extensions: ['md']}],
})

if (filePath) {
const dir = path.dirname(filePath)
const documentDir = path.join(dir, formattedTitle)

if (!fs.existsSync(documentDir)) {
fs.mkdirSync(documentDir)
}

const mediaDir = path.join(documentDir, 'media')
if (!fs.existsSync(mediaDir)) {
fs.mkdirSync(mediaDir)
}

// Save Markdown file
const markdownFilePath = path.join(documentDir, `${formattedTitle}.md`)
fs.writeFile(markdownFilePath, markdownContent, (err) => {
if (err) {
error('Error saving file:', err)
return
}
debug('Markdown file successfully saved:', markdownFilePath)
})

// Save Media files using CID
for (const {url, filename} of mediaFiles) {
const regex = /ipfs:\/\/(.+)/
const match = url.match(regex)
const cid = match ? match[1] : null
const request = net.request(`${API_HTTP_URL}/ipfs/${cid}`)

request.on('response', (response) => {
debug(response)
if (response.statusCode === 200) {
const chunks: Buffer[] = []

response.on('data', (chunk) => {
chunks.push(chunk)
})

response.on('end', () => {
const data = Buffer.concat(chunks)
debug('~~~~~~~~~~~~~HEREEEEEEEEEEEEEEEEE', chunks[0], data.toJSON())
if (!data || data.length === 0) {
error(`Error: No data received for ${filename}`)
return
}

const mediaFilePath = path.join(mediaDir, filename)
try {
fs.writeFileSync(mediaFilePath, data)
debug(`Media file successfully saved: ${mediaFilePath}`)
} catch (e) {
error(`Failed to save media file ${filename}`, e)
}
})
} else {
error(`Error: Invalid status code ${response.statusCode}`)
}
})

request.on('error', (err) => {
error('Error:', err.message)
})

request.end()
}
}
}
12 changes: 10 additions & 2 deletions frontend/packages/app/app-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export type AppContext = {
externalOpen: (url: string) => Promise<void>
windowUtils: WindowUtils
saveCidAsFile: (cid: string, name: string) => Promise<void>
exportDocument: (title: string, markdown: string) => Promise<void>
exportDocument: (
title: string,
markdownContent: string,
mediaFiles: {url: string; filename: string}[],
) => Promise<void>
exportDocuments: (
documents: {
title: string
Expand Down Expand Up @@ -51,7 +55,11 @@ export function AppContextProvider({
externalOpen: (url: string) => Promise<void>
windowUtils: WindowUtils
saveCidAsFile: (cid: string, name: string) => Promise<void>
exportDocument: (title: string, markdown: string) => Promise<void>
exportDocument: (
title: string,
markdownContent: string,
mediaFiles: {url: string; filename: string}[],
) => Promise<void>
exportDocuments: (
documents: {
title: string
Expand Down
7 changes: 6 additions & 1 deletion frontend/packages/app/components/export-doc-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ export const ExportDocButton = ({
const blocks: HMBlockNode[] | undefined =
pub.data?.document?.children
const editorBlocks = toHMBlock(blocks)
exportDocument(title, await convertBlocksToMarkdown(editorBlocks))

const markdownWithFiles =
await convertBlocksToMarkdown(editorBlocks)

const {markdownContent, mediaFiles} = markdownWithFiles
exportDocument(title, markdownContent, mediaFiles)
}}
icon={Download}
>
Expand Down
57 changes: 43 additions & 14 deletions frontend/packages/app/utils/blocks-to-markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,9 @@ function convertContentItemToHtml(contentItem) {
}

function convertBlockToHtml(block) {
let childrenHtml = ''
if (block.children) {
const childrenContent = block.children.map(convertBlockToHtml).join('\n')
if (block.props.childrenType === 'ul') {
childrenHtml = `<ul>${childrenContent}</ul>`
} else if (block.props.childrenType === 'ol') {
childrenHtml = `<ol start="${
block.props.start || 1
}">${childrenContent}</ol>`
} else {
childrenHtml = childrenContent
}
}
const childrenHtml = block.children
? block.children.map(convertBlockToHtml).join('\n')
: ''

switch (block.type) {
case 'heading':
Expand All @@ -68,6 +58,17 @@ function convertBlockToHtml(block) {
return `<p>![${block.props.name}](${block.props.url} "width=${block.props.width}")</p>\n${childrenHtml}`
case 'file':
return `<p>[${block.props.name}](${block.props.url} "size=${block.props.size}")</p>\n${childrenHtml}`
case 'list':
if (block.props.childrenType === 'ul') {
return `<ul>${block.children
.map((child) => `<li>${convertBlockToHtml(child)}</li>`)
.join('\n')}</ul>\n${childrenHtml}`
} else if (block.props.childrenType === 'ol') {
return `<ol start="${block.props.start}">${block.children
.map((child) => `<li>${convertBlockToHtml(child)}</li>`)
.join('\n')}</ol>\n${childrenHtml}`
}
return ''
default:
return block.content
? block.content.map(convertContentItemToHtml).join('') +
Expand All @@ -80,15 +81,43 @@ function convertBlocksToHtml(blocks) {
const htmlContent: string = blocks
.map((block) => convertBlockToHtml(block))
.join('\n\n')
console.log(htmlContent)
return htmlContent
}

async function extractMediaFiles(blocks) {
const mediaFiles: {url: string; filename: string}[] = []
const extractMedia = async (block) => {
if (
block.type === 'image' ||
block.type === 'video' ||
block.type === 'file'
) {
const url = block.props.url
const filename = url.split('/').pop()
mediaFiles.push({url, filename})
block.props = {...block.props, url: `media/${filename}`} // Update the URL to point to the local media folder
}
if (block.children) {
for (const child of block.children) {
await extractMedia(child)
}
}
}
for (const block of blocks) {
await extractMedia(block)
}
return mediaFiles
}

export async function convertBlocksToMarkdown(blocks: HMBlock[]) {
const mediaFiles = await extractMediaFiles(blocks) // Extract media files and update URLs first
const markdownFile = await unified()
.use(rehypeParse, {fragment: true})
.use(rehypeRemark)
.use(remarkGfm)
.use(remarkStringify)
.process(convertBlocksToHtml(blocks))
return markdownFile.value as string
const markdownContent = markdownFile.value as string
return {markdownContent, mediaFiles}
}

0 comments on commit 748078a

Please sign in to comment.