Skip to content

Commit

Permalink
feat: add export to html function
Browse files Browse the repository at this point in the history
  • Loading branch information
1943time committed Jul 12, 2023
1 parent e89cf3d commit 2a0b1f7
Show file tree
Hide file tree
Showing 45 changed files with 220 additions and 50 deletions.
8 changes: 8 additions & 0 deletions src/main/appMenus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const createAppMenus = () => {
openRecent: '打开最近的文件',
clearRecent: '清除',
pdf: '导出 PDF',
html: '导出 HTML',
edit: '编辑',
paragraph: '段落',
titleIncrease: '提升标题',
Expand Down Expand Up @@ -77,6 +78,7 @@ export const createAppMenus = () => {
openRecent: 'Open Recent',
clearRecent: 'Clear Items',
pdf: 'Export To PDF',
html: 'Export To HTML',
edit: 'Edit',
paragraph: 'Paragraph',
titleIncrease: 'Increase Heading Level',
Expand Down Expand Up @@ -181,6 +183,12 @@ export const createAppMenus = () => {
click: (e, win) => {
win?.webContents.send('call-print-pdf')
}
},
{
label: menusLabel.html,
click: (e, win) => {
win?.webContents.send('print-to-html')
}
}
]
},
Expand Down
12 changes: 7 additions & 5 deletions src/main/menus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const registerMenus = () => {
const menusLabel = locale === 'zh' ? {
copyMarkdown: '复制Markdown源码',
pdf: '导出PDF',
html: '导出HTML',
openInFinder: '在Finder中显示',
openInDefault: '默认应用打开',
delete: '删除',
Expand All @@ -25,6 +26,7 @@ export const registerMenus = () => {
} : {
copyMarkdown: 'Copy Markdown Source Code',
pdf: 'Export To PDF',
html: 'Export To HTML',
openInFinder: 'Reveal in Finder',
openInDefault: 'Open in default app',
delete: 'Delete',
Expand Down Expand Up @@ -55,11 +57,11 @@ export const registerMenus = () => {
win?.webContents.send('call-print-pdf')
}
},
// {
// label: '导出html',
// enabled: !!filePath,
// click: (e, win) => win?.webContents.send('print-to-html')
// },
{
label: menusLabel.html,
enabled: filePath?.endsWith('.md'),
click: (e, win) => win?.webContents.send('print-to-html')
},
{
type: 'separator'
},
Expand Down
2 changes: 1 addition & 1 deletion src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const api = {
watchers.set(path, watcher)
},
highlightCodeToString(code: string, lang: string) {
return highlighter?.codeToHtml(code, {lang})
return langSet.has(lang) ? highlighter?.codeToHtml(code, {lang}) : code
},
md5(str: string | Buffer) {
return createHash('md5').update(str).digest('hex')
Expand Down
9 changes: 8 additions & 1 deletion src/renderer/src/components/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import {Nav} from './Nav'
import {treeStore} from '../store/tree'
import {EditorFrame} from '../editor/EditorFrame'
import {useCallback, useEffect, useRef} from 'react'
import {MainApi} from '../api/main'
import {MainApi, saveDialog} from '../api/main'
import {existsSync} from 'fs'
import {Set} from './Set'
import {About} from '../About'
import {exportHtml} from '../editor/output/html'
export const Home = observer(() => {
const initial = useCallback(async () => {
window.electron.ipcRenderer.invoke('get-win-set').then(res => {
Expand Down Expand Up @@ -44,14 +45,20 @@ export const Home = observer(() => {
window.electron.ipcRenderer.send('print-pdf', treeStore.openNote!.filePath, treeStore.root?.filePath)
}
}
const printHtml = () => {
MainApi.sendToSelf('window-blur')
if (treeStore.openNote && treeStore.openNote.ext === 'md') exportHtml(treeStore.openNote)
}
initial()
window.electron.ipcRenderer.on('open', open)
window.electron.ipcRenderer.on('create', create)
window.electron.ipcRenderer.on('call-print-pdf', printPdf)
window.electron.ipcRenderer.on('print-to-html', printHtml)
return () => {
window.electron.ipcRenderer.removeListener('open', open)
window.electron.ipcRenderer.removeListener('create', create)
window.electron.ipcRenderer.removeListener('call-print-pdf', printPdf)
window.electron.ipcRenderer.removeListener('print-to-html', printHtml)
}
}, [])
return (
Expand Down
153 changes: 153 additions & 0 deletions src/renderer/src/editor/output/html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import {getHeadId} from '../../share/sync'
import mermaid from 'mermaid'
import {Node} from 'slate'
import {configStore} from '../../store/config'
import {ipcRenderer} from 'electron'
import {join, parse, isAbsolute, extname} from 'path'
import {treeStore} from '../../store/tree'
import {IFileItem} from '../../index'
import {saveDialog} from '../../api/main'
import katex from 'katex'

export const exportHtml = async (node: IFileItem) => {
const tree = treeStore.schemaMap.get(node)?.state || []
const title = parse(node.filePath).name
const env = await ipcRenderer.invoke('get-env') as {webPath: string}
const content = await transform(tree)
const libs = await window.api.fs.readdir(join(env.webPath, 'lib'))
const style = libs.find(l => /index-\w+\.css$/.test(l))
let styleCode = ''
if (style) styleCode = await window.api.fs.readFile(join(env.webPath, 'lib', style), {encoding: 'utf-8'})
const html = `<html lang="en" class="${configStore.config.dark ? 'dark' : ''}">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/svg+xml" href="/icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<style>${styleCode}</style>
</head>
<body>
<div id="root">
<div class="doc-container">
<div class="content">
${content}
</div>
${getOutline(tree)}
</div>
</div>
</body>
</html>
`
const save = await saveDialog({
filters: [{name: 'html', extensions: ['html']}]
})
if (save.filePath) {
window.api.fs.writeFile(save.filePath, html, {encoding: 'utf-8'})
}
}

const getOutline = (schema: any[]) => {
const list = schema.filter(n => n.level > 1 && n.level < 5).map(h => {
const id = getHeadId(h)
return `<div class="leading-item " style="padding-left: ${(h.level - 2) * 16}px;"><a href="#${id}">${id}</a></div>`
})
if (!list.length) return ''
return `<div class="leading lg:block">
<div class="leading-title">On this page</div>
<div class="leading-list">
${list.join('')}
</div>`
}
const transform = async (schema: any[]) => {
let str = ''
for (let e of schema) {
switch (e.type) {
case 'head':
const id = getHeadId(e)
str += `<a href="#${id}" class="heading duration-200 hover:text-sky-500"><h${e.level}><span class="anchor" id="${id}" style="top:-10px"></span>${await transform(e.children || [])}</h${e.level}></a>`
break
case 'paragraph':
str += `<p>${await transform(e.children || [])}</p>`
break
case 'blockquote':
str += `<blockquote>${await transform(e.children || [])}</blockquote>`
break
case 'hr':
str += '<hr class="m-hr"/>'
break
case 'list':
let tag = e.order ? 'ol' : 'ul'
str += `<${tag} class="m-list">${await transform(e.children || [])}</${tag}>`
break
case 'list-item':
const task = typeof e.checked === 'boolean'
str += `<li class="m-list-item ${task ? 'task' : ''}">${task ? `<span class="absolute left-0" style="top:7px"><input type="checkbox" ${e.checked ? 'checked' : ''} class="w-[14px] h-[14px] align-baseline"></span>` : ''}${await transform(e.children || [])}</li>`
break
case 'table':
let head = ''
for (let h of e.children[0].children) {
head += `<th>${await transform(h.children || [])}</th>`
}
str += `<table><thead><tr>${head}</tr></thead><tbody>${await transform(e.children?.slice(1) || [])}</tbody></table>`
break
case 'table-row':
str += `<tr>${await transform(e.children)}</tr>`
break
case 'table-cell':
str += `<td>${await transform(e.children)}</td>`
break
case 'media':
let url = e.url
if (e.url && !e.url.startsWith('http')) {
try {
const realPath = isAbsolute(e.url) ? e.url : join(treeStore.openNote!.filePath, '..', e.url)
if (await window.api.fs.lstat(realPath)) {
const code = await window.api.fs.readFile(realPath, {encoding: 'base64'})
url = `data:image/${parse(realPath).ext.slice(1)};base64,${code}`
}
} catch (e) {}
}
str += `<img alt="${e.alt}" src="${url}"/>`
break
case 'code':
const code = e.children.map(n => Node.string(n)).join('\n')
if (e.language === 'mermaid') {
const res = await mermaid.render('m' + Math.floor(Math.random() * 1000), code).catch(e => null)
str += `<div class="mermaid-container pre">${res?.svg}</div>`
} else if (e.katex) {
try {
const res = katex.renderToString(code, {
strict: false,
output: 'mathml',
throwOnError: false,
displayMode: true,
macros: {
"\\f": "#1f(#2)"
}
})
str += `<div class="py-2 mb-4">${res}</div>`
} catch (e) {}
} else {
const code = e.children.map(n => Node.string(n)).join('\n')
let codeHtml = window.api.highlightCodeToString(code, e.language)
codeHtml = codeHtml.replace(/<\/?pre[^>]*>/g, '').replace(/<\/?code>/, '')
str += `<div class="relative mb-4">
<div class="absolute z-10 right-2 top-1 flex items-center select-none"><div class="text-gray-400 text-xs"><span>${e.language}</span></div></div>
<pre style="padding: 10px 0" class="code-highlight relative tab-${configStore.config.codeTabSize}" data-lang="${e.language}">
<code>${codeHtml}</code>
</pre></div>`
}
break
}
if (e.text) {
let text = e.text
if (e.strikethrough) text = `<del>${text}</del>`
if (e.bold) text = `<strong>${text}</strong>`
if (e.code) text = `<code class="inline-code">${text}</code>`
if (e.italic) text = `<i>${text}</i>`
if (e.url) text = `<a href="${e.url}" target="_blank">${text}</a>`
str += text
}
}
return str
}
2 changes: 1 addition & 1 deletion src/renderer/src/editor/tools/Leading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const Heading = observer(({note}: {
}, [])
return (
<div
className={`${configStore.config.showLeading ? '' : 'hidden'} sticky ${store.openSearch ? 'top-[46px] h-[calc(100vh_-_86px)]' : 'top-0 h-[calc(100vh_-_40px)]'} pl-4 border-l b1 flex-shrink-0`}
className={`${configStore.config.showLeading ? 'lg:block' : ''} hidden sticky ${store.openSearch ? 'top-[46px] h-[calc(100vh_-_86px)]' : 'top-0 h-[calc(100vh_-_40px)]'} pl-4 border-l b1 flex-shrink-0`}
ref={e => {
box.current = e?.parentElement?.parentElement?.parentElement || undefined
}}
Expand Down
2 changes: 1 addition & 1 deletion web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BlueStone</title>
<script type="module" crossorigin src="/lib/index-dcd10135.js"></script>
<script type="module" crossorigin src="/lib/index-5fdd6735.js"></script>
<link rel="stylesheet" href="/lib/index-c0239034.css">
</head>
<body>
Expand Down
Loading

0 comments on commit 2a0b1f7

Please sign in to comment.