From 5bcf8c68fde891a37640387e77f5a0a76bfadd3f Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 12 Jun 2025 07:43:30 +0300 Subject: [PATCH] feat(markdown): add embedding image --- package.json | 2 + pnpm-lock.yaml | 20 ++++-- src/main/index.ts | 1 + src/main/ipc/handlers/fs.ts | 33 ++++++++++ src/main/ipc/index.ts | 2 + src/main/types/ipc.ts | 7 ++ src/renderer/components/editor/Editor.vue | 28 ++++++++ .../components/editor/markdown/Markdown.vue | 65 ++++++++++--------- src/renderer/index.html | 1 - 9 files changed, 125 insertions(+), 34 deletions(-) create mode 100644 src/main/ipc/handlers/fs.ts diff --git a/package.json b/package.json index 0ebbac53..fa78cd73 100644 --- a/package.json +++ b/package.json @@ -60,11 +60,13 @@ "markmap-lib": "^0.18.11", "markmap-view": "^0.18.10", "mermaid": "^11.6.0", + "nanoid": "^3.3.8", "nearest-color": "^0.4.4", "onigasm": "^2.2.5", "prettier": "^3.5.3", "radix-vue": "^1.9.17", "sanitize-html": "^2.15.0", + "slash": "^3.0.0", "slugify": "^1.6.6", "tailwind-merge": "^3.0.2", "toml": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee80af0d..ecde7d94 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ importers: mermaid: specifier: ^11.6.0 version: 11.6.0 + nanoid: + specifier: ^3.3.8 + version: 3.3.8 nearest-color: specifier: ^0.4.4 version: 0.4.4 @@ -107,6 +110,9 @@ importers: sanitize-html: specifier: ^2.15.0 version: 2.15.0 + slash: + specifier: ^3.0.0 + version: 3.0.0 slugify: specifier: ^1.6.6 version: 1.6.6 @@ -3688,8 +3694,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.2: - resolution: {integrity: sha512-b+CiXQCNMUGe0Ri64S9SXFcP9hogjAJ2Rd6GdVxhPLRm7mhGaM7VgOvCAJ1ZshfHbqVDI3uqTI5C8/GaKuLI7g==} + nanoid@5.1.5: + resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} engines: {node: ^18 || >=20} hasBin: true @@ -4368,6 +4374,10 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + slashes@3.0.12: resolution: {integrity: sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==} @@ -9004,7 +9014,7 @@ snapshots: nanoid@3.3.8: {} - nanoid@5.1.2: {} + nanoid@5.1.5: {} napi-build-utils@2.0.0: {} @@ -9389,7 +9399,7 @@ snapshots: aria-hidden: 1.2.4 defu: 6.1.4 fast-deep-equal: 3.1.3 - nanoid: 5.1.2 + nanoid: 5.1.5 vue: 3.5.13(typescript@5.7.3) transitivePeerDependencies: - '@vue/composition-api' @@ -9653,6 +9663,8 @@ snapshots: sisteransi@1.0.5: {} + slash@3.0.0: {} + slashes@3.0.12: {} slice-ansi@3.0.0: diff --git a/src/main/index.ts b/src/main/index.ts index 5c81ef25..e48c56aa 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -25,6 +25,7 @@ function createWindow() { webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: true, + webSecurity: false, }, }) diff --git a/src/main/ipc/handlers/fs.ts b/src/main/ipc/handlers/fs.ts new file mode 100644 index 00000000..ae96c415 --- /dev/null +++ b/src/main/ipc/handlers/fs.ts @@ -0,0 +1,33 @@ +import { Buffer } from 'node:buffer' +import { join, parse } from 'node:path' +import { ipcMain } from 'electron' +import { ensureDirSync, writeFileSync } from 'fs-extra' +import { nanoid } from 'nanoid' +import slash from 'slash' +import { store } from '../../store' + +const ASSETS_DIR = 'assets' + +export function registerFsHandlers() { + ipcMain.handle('fs:assets', (event, { buffer, fileName }) => { + const storagePath = store.preferences.get('storagePath') + + return new Promise((resolve, reject) => { + try { + const assetsPath = join(storagePath, ASSETS_DIR) + + const { ext } = parse(fileName) + const name = `${nanoid()}${ext}` + const dest = join(assetsPath, name) + + ensureDirSync(assetsPath) + writeFileSync(dest, Buffer.from(buffer)) + + resolve(slash(join(ASSETS_DIR, name))) + } + catch (error) { + reject(error) + } + }) + }) +} diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index 316688df..37db2b0c 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -2,6 +2,7 @@ import type { Channel } from '../types/ipc' import { BrowserWindow } from 'electron' import { registerDBHandlers } from './handlers/db' import { registerDialogHandlers } from './handlers/dialog' +import { registerFsHandlers } from './handlers/fs' import { registerPrettierHandlers } from './handlers/prettier' import { registerSystemHandlers } from './handlers/system' @@ -14,4 +15,5 @@ export function registerIPC() { registerDBHandlers() registerSystemHandlers() registerPrettierHandlers() + registerFsHandlers() } diff --git a/src/main/types/ipc.ts b/src/main/types/ipc.ts index 48c78cd0..41d0bc73 100644 --- a/src/main/types/ipc.ts +++ b/src/main/types/ipc.ts @@ -24,17 +24,20 @@ type MainMenuAction = type DBAction = 'relaod' | 'move' | 'migrate' | 'clear' type SystemAction = 'reload' | 'open-external' type PrettierAction = 'format' +type FsAction = 'assets' export type MainMenuChannel = CombineWith export type DBChannel = CombineWith export type SystemChannel = CombineWith export type PrettierChannel = CombineWith +export type FsChannel = CombineWith export type Channel = | MainMenuChannel | DBChannel | SystemChannel | PrettierChannel + | FsChannel export interface DialogOptions { properties?: OpenDialogOptions['properties'] @@ -45,3 +48,7 @@ export interface PrettierOptions { text: string parser: string } + +export interface FsAssetsOptions { + path: string +} diff --git a/src/renderer/components/editor/Editor.vue b/src/renderer/components/editor/Editor.vue index fa2f937c..15c190b4 100644 --- a/src/renderer/components/editor/Editor.vue +++ b/src/renderer/components/editor/Editor.vue @@ -150,6 +150,34 @@ async function init() { editor.on('scroll', hideScrollbar) + editor.on('drop', async (cm, e) => { + if (selectedSnippetContent.value?.language === 'markdown') { + const file = e.dataTransfer?.files[0] + + if (!file) + return + + if (!file.type.startsWith('image/')) + return + + try { + const arrayBuffer = await file.arrayBuffer() + const buffer = Array.from(new Uint8Array(arrayBuffer)) + + // Вызываем IPC хендлер для сохранения файла из буфера + const relativePath = await ipc.invoke('fs:assets', { + buffer, + fileName: file.name, + }) + + cm.replaceSelection(`![${file.name}](./${relativePath})`) + } + catch (error) { + console.error('Ошибка при добавлении изображения:', error) + } + } + }) + ipc.on('main-menu:font-size-increase', () => { settings.fontSize++ fontSize.value = `${settings.fontSize}px` diff --git a/src/renderer/components/editor/markdown/Markdown.vue b/src/renderer/components/editor/markdown/Markdown.vue index 066b940b..b5a17dc4 100644 --- a/src/renderer/components/editor/markdown/Markdown.vue +++ b/src/renderer/components/editor/markdown/Markdown.vue @@ -1,6 +1,6 @@