Skip to content

Commit 114f322

Browse files
feat(markdown): add embedding image (#561)
1 parent 146dba1 commit 114f322

9 files changed

Lines changed: 125 additions & 34 deletions

File tree

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,13 @@
6060
"markmap-lib": "^0.18.11",
6161
"markmap-view": "^0.18.10",
6262
"mermaid": "^11.6.0",
63+
"nanoid": "^3.3.8",
6364
"nearest-color": "^0.4.4",
6465
"onigasm": "^2.2.5",
6566
"prettier": "^3.5.3",
6667
"radix-vue": "^1.9.17",
6768
"sanitize-html": "^2.15.0",
69+
"slash": "^3.0.0",
6870
"slugify": "^1.6.6",
6971
"tailwind-merge": "^3.0.2",
7072
"toml": "^3.0.0",

pnpm-lock.yaml

Lines changed: 16 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ function createWindow() {
2525
webPreferences: {
2626
preload: path.join(__dirname, 'preload.js'),
2727
nodeIntegration: true,
28+
webSecurity: false,
2829
},
2930
})
3031

src/main/ipc/handlers/fs.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Buffer } from 'node:buffer'
2+
import { join, parse } from 'node:path'
3+
import { ipcMain } from 'electron'
4+
import { ensureDirSync, writeFileSync } from 'fs-extra'
5+
import { nanoid } from 'nanoid'
6+
import slash from 'slash'
7+
import { store } from '../../store'
8+
9+
const ASSETS_DIR = 'assets'
10+
11+
export function registerFsHandlers() {
12+
ipcMain.handle('fs:assets', (event, { buffer, fileName }) => {
13+
const storagePath = store.preferences.get('storagePath')
14+
15+
return new Promise((resolve, reject) => {
16+
try {
17+
const assetsPath = join(storagePath, ASSETS_DIR)
18+
19+
const { ext } = parse(fileName)
20+
const name = `${nanoid()}${ext}`
21+
const dest = join(assetsPath, name)
22+
23+
ensureDirSync(assetsPath)
24+
writeFileSync(dest, Buffer.from(buffer))
25+
26+
resolve(slash(join(ASSETS_DIR, name)))
27+
}
28+
catch (error) {
29+
reject(error)
30+
}
31+
})
32+
})
33+
}

src/main/ipc/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Channel } from '../types/ipc'
22
import { BrowserWindow } from 'electron'
33
import { registerDBHandlers } from './handlers/db'
44
import { registerDialogHandlers } from './handlers/dialog'
5+
import { registerFsHandlers } from './handlers/fs'
56
import { registerPrettierHandlers } from './handlers/prettier'
67
import { registerSystemHandlers } from './handlers/system'
78

@@ -14,4 +15,5 @@ export function registerIPC() {
1415
registerDBHandlers()
1516
registerSystemHandlers()
1617
registerPrettierHandlers()
18+
registerFsHandlers()
1719
}

src/main/types/ipc.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,20 @@ type MainMenuAction =
2424
type DBAction = 'relaod' | 'move' | 'migrate' | 'clear'
2525
type SystemAction = 'reload' | 'open-external'
2626
type PrettierAction = 'format'
27+
type FsAction = 'assets'
2728

2829
export type MainMenuChannel = CombineWith<MainMenuAction, 'main-menu'>
2930
export type DBChannel = CombineWith<DBAction, 'db'>
3031
export type SystemChannel = CombineWith<SystemAction, 'system'>
3132
export type PrettierChannel = CombineWith<PrettierAction, 'prettier'>
33+
export type FsChannel = CombineWith<FsAction, 'fs'>
3234

3335
export type Channel =
3436
| MainMenuChannel
3537
| DBChannel
3638
| SystemChannel
3739
| PrettierChannel
40+
| FsChannel
3841

3942
export interface DialogOptions {
4043
properties?: OpenDialogOptions['properties']
@@ -45,3 +48,7 @@ export interface PrettierOptions {
4548
text: string
4649
parser: string
4750
}
51+
52+
export interface FsAssetsOptions {
53+
path: string
54+
}

src/renderer/components/editor/Editor.vue

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,34 @@ async function init() {
150150
151151
editor.on('scroll', hideScrollbar)
152152
153+
editor.on('drop', async (cm, e) => {
154+
if (selectedSnippetContent.value?.language === 'markdown') {
155+
const file = e.dataTransfer?.files[0]
156+
157+
if (!file)
158+
return
159+
160+
if (!file.type.startsWith('image/'))
161+
return
162+
163+
try {
164+
const arrayBuffer = await file.arrayBuffer()
165+
const buffer = Array.from(new Uint8Array(arrayBuffer))
166+
167+
// Вызываем IPC хендлер для сохранения файла из буфера
168+
const relativePath = await ipc.invoke('fs:assets', {
169+
buffer,
170+
fileName: file.name,
171+
})
172+
173+
cm.replaceSelection(`![${file.name}](./${relativePath})`)
174+
}
175+
catch (error) {
176+
console.error('Ошибка при добавлении изображения:', error)
177+
}
178+
}
179+
})
180+
153181
ipc.on('main-menu:font-size-increase', () => {
154182
settings.fontSize++
155183
fontSize.value = `${settings.fontSize}px`

src/renderer/components/editor/markdown/Markdown.vue

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import { useSnippets } from '@/composables'
3-
import { i18n, ipc } from '@/electron'
3+
import { i18n, ipc, store } from '@/electron'
44
import { useDark } from '@vueuse/core'
55
import CodeMirror from 'codemirror'
66
import { Minus, Plus } from 'lucide-vue-next'
@@ -14,6 +14,8 @@ import 'codemirror/addon/runmode/runmode'
1414
import 'codemirror/theme/neo.css'
1515
import 'codemirror/theme/oceanic-next.css'
1616
17+
const isDev = import.meta.env.DEV
18+
1719
const { selectedSnippetContent } = useSnippets()
1820
const isDark = useDark()
1921
const { scaleToShow, onZoom } = useMarkdown()
@@ -58,7 +60,7 @@ async function renderMarkdown() {
5860
if (selectedSnippetContent.value?.value) {
5961
const markdownHtml = await marked.parse(selectedSnippetContent.value.value)
6062
61-
const sanitizedHtml = sanitizeHtml(markdownHtml, {
63+
let sanitizedHtml = sanitizeHtml(markdownHtml, {
6264
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'del']),
6365
allowedAttributes: {
6466
'*': [
@@ -81,6 +83,13 @@ async function renderMarkdown() {
8183
allowedSchemes: ['http', 'https', 'masscode'],
8284
})
8385
86+
const re = /src="\.\//g
87+
const path = store.preferences.get('storagePath')
88+
89+
sanitizedHtml = isDev
90+
? sanitizedHtml.replace(re, `src="file://${path}/`)
91+
: sanitizedHtml.replace(re, `src="${path}/`)
92+
8493
renderedContent.value = sanitizedHtml
8594
}
8695
else {
@@ -167,35 +176,33 @@ watch(isDark, (value) => {
167176
</script>
168177

169178
<template>
170-
<div>
171-
<EditorHeaderTool v-if="isShowHeaderTool">
172-
<div class="flex w-full justify-end gap-2 px-2">
173-
<UiActionButton
174-
:tooltip="i18n.t('button.zoomOut')"
175-
@click="onZoom('out')"
176-
>
177-
<Minus class="h-3 w-3" />
178-
</UiActionButton>
179-
<div class="tabular-nums select-none">
180-
{{ scaleToShow }}
181-
</div>
182-
<UiActionButton
183-
:tooltip="i18n.t('button.zoomIn')"
184-
@click="onZoom('in')"
185-
>
186-
<Plus class="h-3 w-3" />
187-
</UiActionButton>
188-
</div>
189-
</EditorHeaderTool>
190-
<PerfectScrollbar :options="{ minScrollbarLength: 20 }">
191-
<div
192-
ref="markdownRef"
193-
class="markdown-content"
179+
<EditorHeaderTool v-if="isShowHeaderTool">
180+
<div class="flex w-full justify-end gap-2 px-2">
181+
<UiActionButton
182+
:tooltip="i18n.t('button.zoomOut')"
183+
@click="onZoom('out')"
194184
>
195-
<div v-html="renderedContent" />
185+
<Minus class="h-3 w-3" />
186+
</UiActionButton>
187+
<div class="tabular-nums select-none">
188+
{{ scaleToShow }}
196189
</div>
197-
</PerfectScrollbar>
198-
</div>
190+
<UiActionButton
191+
:tooltip="i18n.t('button.zoomIn')"
192+
@click="onZoom('in')"
193+
>
194+
<Plus class="h-3 w-3" />
195+
</UiActionButton>
196+
</div>
197+
</EditorHeaderTool>
198+
<PerfectScrollbar :options="{ minScrollbarLength: 20 }">
199+
<div
200+
ref="markdownRef"
201+
class="markdown-content"
202+
>
203+
<div v-html="renderedContent" />
204+
</div>
205+
</PerfectScrollbar>
199206
</template>
200207

201208
<style>

src/renderer/index.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<meta http-equiv="Content-Security-Policy" content="default-src * self blob: data: gap:; style-src * self 'unsafe-inline' blob: data: gap:; script-src * 'self' 'unsafe-eval' 'unsafe-inline' blob: data: gap:; object-src * 'self' blob: data: gap:; img-src * self 'unsafe-inline' blob: data: gap:; connect-src * self 'unsafe-inline' blob: data: gap:; frame-src * self blob: data: gap:;">
76
<title>massCode</title>
87
</head>
98
<body>

0 commit comments

Comments
 (0)