diff --git a/.github/workflows/mas.yml b/.github/workflows/mas.yml deleted file mode 100644 index 88fa9daf..00000000 --- a/.github/workflows/mas.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Package - -on: - push: - branches: - - mac-store - workflow_dispatch: - -jobs: - build: - strategy: - matrix: - include: - - os: macos-latest - platform: 'mac' - - runs-on: ${{ matrix.os }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - permissions: - contents: write - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup Node.js environment - uses: actions/setup-node@v2.1.2 - with: - node-version: 16.x - - - name: Install pnpm - uses: pnpm/action-setup@v2 - with: - version: 8 - - - name: Install - run: | - pnpm install --no-frozen-lockfile - - - name: Add MacOS certs - if: matrix.platform == 'mac' - run: | - chmod +x osx-cert.sh && ./osx-cert.sh - echo -n "${{ secrets.PROVISION }}" | base64 --decode > build/bluestone.provisionprofile - echo "APPLEID=${{ secrets.APPLEID }}" >> $GITHUB_ENV - echo "APPLEIDPASS=${{ secrets.APPLEIDPASS }}" >> $GITHUB_ENV - echo "APPLETEAMID=${{ secrets.APPLETEAMID }}" >> $GITHUB_ENV - env: - CERTIFICATE_OSX_APPLICATION: ${{ secrets.CERTIFICATE_OSX_APPLICATION }} - CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }} - CERTIFICATE_OSX_INSTALL: ${{secrets.CSC_INSTALLER_LINK}} - INSTALL_PASSWORD: ${{secrets.CSC_INSTALLER_KEY_PASSWORD}} - CERTIFICATE_DISTRIBUTION: ${{secrets.CERTIFICATE_DISTRIBUTION}} -# - name: Set env -# if: matrix.platform == 'mac' -# run: | -# echo "APPLEID=${{ secrets.APPLEID }}" >> $GITHUB_ENV -# echo "APPLEIDPASS=${{ secrets.APPLEIDPASS }}" >> $GITHUB_ENV -# echo "CSC_LINK=${{ secrets.CERTIFICATE_OSX_APPLICATION }}" >> $GITHUB_ENV -# echo "CSC_KEY_PASSWORD=${{ secrets.CERTIFICATE_PASSWORD }}" >> $GITHUB_ENV -# echo "CSC_INSTALLER_LINK=${{ secrets.CSC_INSTALLER_LINK }}" >> $GITHUB_ENV -# echo "CSC_INSTALLER_KEY_PASSWORD=${{ secrets.CSC_INSTALLER_KEY_PASSWORD }}" >> $GITHUB_ENV -# echo "APPLETEAMID=${{ secrets.APPLETEAMID }}" >> $GITHUB_ENV -# echo -n "${{ secrets.PROVISION }}" | base64 --decode > build/bluestone.provisionprofile - - # - name: ls - # run: | - # wget https://github.com/1943time/bs-web/releases/download/v0.5.1/dist.zip - # chmod 755 dist.zip - # unzip dist.zip - # mv -f dist web - - name: Build - run: | - npm run build - - - name: Electron-Mac - if: matrix.platform == 'mac' - run: | - npm run build:mac -# - name: GH Release -# uses: softprops/action-gh-release@v0.1.15 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# draft: false -# prerelease: true -# files: | -# dist/Bluestone*.* -# dist/latest*.yml diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 37852462..faf2d961 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -51,7 +51,7 @@ jobs: - name: ls run: | - wget https://github.com/1943time/bs-web/releases/download/v0.5.1/web.zip + wget https://pic.bluestone.blog/web/web.zip chmod 755 web.zip unzip web.zip - name: Build diff --git a/.gitignore b/.gitignore index 94720520..41c891aa 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ build/entitlements.mas.plist build/entitlements.mas.loginhelper.plist src/renderer/src/share mas-builder.yml +src/main/auth.ts diff --git a/electron-builder.yml b/electron-builder.yml index 96135140..09f8720d 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -18,8 +18,8 @@ files: asarUnpack: - resources/* artifactName: "Bluestone-${os}-${arch}-${version}.${ext}" -#extraResources: -# - web +extraResources: + - web fileAssociations: ext: md role: Editor @@ -42,7 +42,7 @@ mac: target: - target: dmg arch: - - x64 +# - x64 - arm64 linux: target: @@ -56,3 +56,8 @@ publish: provider: github owner: '1943time' repo: 'bluestone' + +protocols: + name: Bluestone Markdown + schemes: + - bluestone-markdown diff --git a/package.json b/package.json index c6626e5f..1fc18520 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bluestone", - "version": "0.8.0", + "version": "0.8.1", "description": "", "main": "./out/main/index.js", "license": "AGPL-3.0", @@ -16,7 +16,7 @@ "build": "npm run typecheck && electron-vite build", "postinstall": "electron-builder install-app-deps", "build:win": "electron-builder --win --arm64 --x64 --config", - "build:mac": "electron-builder --macos --arm64 --x64 --config", + "build:mac": "electron-builder --macos --config", "build:mas": "npm run build && electron-builder --macos --config mas-builder.yml", "build:linux": "electron-builder --linux --config", "unpackage": "rm -rf unpackage && asar extract dist/mac-arm64/bluestone.app/Contents/Resources/app.asar ./unpackage", diff --git a/src/main/api.ts b/src/main/api.ts index c0632423..d4758afe 100644 --- a/src/main/api.ts +++ b/src/main/api.ts @@ -10,6 +10,7 @@ export const baseUrl = is.dev && process.env['ELECTRON_RENDERER_URL'] ? process. const workerPath = join(__dirname, '../renderer/worker.html') import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions import fetch from 'node-fetch' +import {openAuth} from './auth' export const windowOptions: BrowserWindowConstructorOptions = { show: false, @@ -50,6 +51,9 @@ export const registerApi = () => { ipcMain.handle('get-path', (e, type: Parameters[0]) => { return app.getPath(type) }) + ipcMain.on('open-auth', (e, type: 'github') => { + openAuth(type) + }) ipcMain.handle('get-env', () => { return { isPackaged: app.isPackaged, diff --git a/src/main/appMenus.ts b/src/main/appMenus.ts index d5a58e65..c01a78fe 100644 --- a/src/main/appMenus.ts +++ b/src/main/appMenus.ts @@ -55,7 +55,6 @@ export const createAppMenus = () => { italic: 'Italic', strikethrough: 'Strikethrough', inlineCode: 'Inline Code', - insertPicture: 'Insert Picture', clear: 'Clear', view: 'View', zoomIn: 'Zoom In', @@ -164,9 +163,19 @@ export const createAppMenus = () => { {type: 'separator'}, { label: menusLabel.pdf, + id: 'print-pdf', + enabled: false, click: (e, win) => { win?.webContents.send('call-print-pdf') } + }, + { + label: menusLabel.html, + id: 'print-html', + enabled: false, + click: (e, win) => { + win?.webContents.send('call-print-html') + } } ] }) @@ -309,19 +318,32 @@ export const createAppMenus = () => { click: task('code') }, { - label: menusLabel.insertPicture, + label: 'Image', accelerator: `${cmd}+p`, - click: (e, win) => { - dialog.showOpenDialog({ - properties: ['openFile'], - filters: [{extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp'], name: 'Image'}], - securityScopedBookmarks: true - }).then(res => { - if (res.filePaths.length) { - win?.webContents.send('key-task', 'insertImage', res.filePaths[0]) + submenu: [ + { + label: 'Insert local image', + accelerator: `${cmd}+p`, + click: (e, win) => { + dialog.showOpenDialog({ + properties: ['openFile'], + filters: [{extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp'], name: 'Image'}], + securityScopedBookmarks: true + }).then(res => { + if (res.filePaths.length) { + win?.webContents.send('key-task', 'insertImage', res.filePaths[0]) + } + }) } - }) - } + }, + { + accelerator: `${cmd}+shift+p`, + label: 'Insert image via url', + click: (e, win) => { + win?.webContents.send('key-task', 'insertNetworkImage') + } + } + ] }, {type: 'separator'}, { @@ -347,8 +369,6 @@ export const createAppMenus = () => { } ] const devTools:MenuOptions[number]['submenu'] = is.dev ? [ - {type: 'separator'}, - {role: 'reload'}, {role: 'toggleDevTools'} ] : [] menus.push( @@ -379,6 +399,8 @@ export const createAppMenus = () => { BrowserWindow.getFocusedWindow()?.webContents.send('open-search') } }, + {type: 'separator'}, + {role: 'reload'}, ...devTools ], } @@ -459,4 +481,8 @@ export const createAppMenus = () => { break } }) + ipcMain.on('open-file', (e, isMarkdown: boolean) => { + instance.getMenuItemById('print-pdf')!.enabled = isMarkdown + instance.getMenuItemById('print-html')!.enabled = isMarkdown + }) } diff --git a/src/main/index.ts b/src/main/index.ts index eba6638c..82ecc727 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,6 +5,7 @@ import {baseUrl, isDark, registerApi, windowOptions} from './api' import {createAppMenus} from './appMenus' import {registerMenus} from './menus' import {store} from './store' +import log from 'electron-log' import {AppUpdate} from './update' const isWindows = process.platform === 'win32' @@ -18,6 +19,7 @@ type WinOptions = { } const windows = new Map() +app.setAsDefaultProtocolClient('bluestone-markdown') function createWindow(initial?: WinOptions): void { const dark = isDark() const {width, height} = screen.getPrimaryDisplay().workAreaSize @@ -84,6 +86,10 @@ app.on('will-finish-launching', () => { openFiles(file) } }) + app.on('open-url', (event, url) => { + log.info('open-url', url) + BrowserWindow.getFocusedWindow()?.webContents.send('open-schema', url) + }) }) const openFiles = (filePath: string) => { try { diff --git a/src/main/menus.ts b/src/main/menus.ts index d9124642..42d62735 100644 --- a/src/main/menus.ts +++ b/src/main/menus.ts @@ -42,6 +42,13 @@ export const registerMenus = () => { win?.webContents.send('call-print-pdf') } }, + { + label: menusLabel.html, + enabled: filePath?.endsWith('.md'), + click: (e, win) => { + win?.webContents.send('call-print-html') + } + }, { type: 'separator' }, diff --git a/src/renderer/src/api/main.ts b/src/renderer/src/api/main.ts index d728eb2a..26aeebf4 100644 --- a/src/renderer/src/api/main.ts +++ b/src/renderer/src/api/main.ts @@ -22,6 +22,9 @@ export const MainApi = { closeWindow() { ipcRenderer.send('close-window') }, + openAuth(type: 'github') { + ipcRenderer.send('open-auth', type) + }, getSystemDark() { return ipcRenderer.invoke('get-system-dark') }, diff --git a/src/renderer/src/components/AceCode.tsx b/src/renderer/src/components/AceCode.tsx new file mode 100644 index 00000000..d8d0edf1 --- /dev/null +++ b/src/renderer/src/components/AceCode.tsx @@ -0,0 +1,26 @@ +import AceEditor from 'react-ace' + +export function AceCode(props: { + value?: string + onChange?: (v: string) => void +}) { + return ( +
+ +
+ ) +} diff --git a/src/renderer/src/components/ExportEbook.tsx b/src/renderer/src/components/ExportEbook.tsx deleted file mode 100644 index 926f3195..00000000 --- a/src/renderer/src/components/ExportEbook.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import {observer} from 'mobx-react-lite' -import {Alert, Button, Form, Input, Modal, Radio} from 'antd' -import {configStore} from '../store/config' -import {ReadOutlined} from '@ant-design/icons' -import {useLocalState} from '../hooks/useLocalState' -import AceEditor from 'react-ace' -import 'ace-builds/src-noconflict/mode-json' -import 'ace-builds/src-noconflict/theme-cloud9_night' -import {exportEbookHtml} from '../editor/output/html' -import {db, Ebook} from '../store/db' -import {treeStore} from '../store/tree' -import {useCallback, useEffect} from 'react' -import {message$} from '../utils' - -export function AceCode(props: { - value?: string - onChange?: (v: string) => void -}) { - return ( -
- -
- ) -} - -export const ExportEbook = observer(() => { - const [state, setState] = useLocalState({ - open: false, - addOpen: false, - exporting: false, - selectRecord: undefined as Ebook | undefined, - records: [] as Ebook[] - }) - const getEbook = useCallback(() => { - db.ebook.where('filePath').equals(treeStore.root?.filePath).toArray().then(res => { - setState({records: res}) - }) - }, []) - useEffect(() => { - window.electron.ipcRenderer.on('export-ebook', () => { - if (treeStore.root) { - setState({open: true}) - getEbook() - } else { - message$.next({ - type: 'info', - content: configStore.isZh ? '导出电子书需要打开任意文件夹' : 'Exporting an eBook requires opening any folder' - }) - } - }) - }, []) - return ( - setState({open: false})} - title={configStore.isZh ? '导出电子书为HTML' : 'Export eBook to html'} - footer={null} - > - -
- {state.records.map(r => -
-
- - {r.name} -
-
- - - -
-
- )} -
- - { - getEbook() - setState({addOpen: false}) - }} - record={state.selectRecord} - /> -
- ) -}) - -const Add = observer((props: { - onClose: () => void - open: boolean, - record?: Ebook -}) => { - const [form] = Form.useForm() - const [state, setState] = useLocalState({ - loading: false - }) - useEffect(() => { - if (props.record) { - form.setFieldsValue(props.record) - } else { - form.resetFields() - } - }, [props.open]) - return ( - { - form.validateFields().then(v => { - setState({loading: true}) - exportEbookHtml(v).then(() => { - if (props.record) { - db.ebook.update(props.record.id!, { - ...v - }) - } else { - db.ebook.add({ - filePath: treeStore.root.filePath!, - ...v - }) - } - props.onClose() - }).finally(() => setState({loading: false})) - }) - }} - > -
- - - - - - {configStore.isZh ? '根据文件目录自动同步' : 'Automatic export on this directory'} - {configStore.isZh ? '自定义章节' : 'Custom chapters'} - - - prevValues.strategy !== nextValues.strategy}> - {() => - <> - {form.getFieldValue('strategy') === 'auto' && - <> - - - - - } - {form.getFieldValue('strategy') === 'custom' && - { - try { - if (!(JSON.parse(value) instanceof Array)) { - return Promise.reject(configStore.isZh ? 'JSON格式不正确' : 'The JSON format is incorrect') - } - } catch (e) { - return Promise.reject(configStore.isZh ? 'JSON格式不正确' : 'The JSON format is incorrect') - } - return Promise.resolve() - }} - ]} - initialValue={JSON.stringify([ - {name: "folderName", folder: true, children: [{name: "docName", path: "folderName/fileName"}]} - ], null, 2)} - > - - - } - - } - -
-
- ) -}) diff --git a/src/renderer/src/components/FullSearch.tsx b/src/renderer/src/components/FullSearch.tsx index a5a364e0..39264d84 100644 --- a/src/renderer/src/components/FullSearch.tsx +++ b/src/renderer/src/components/FullSearch.tsx @@ -163,7 +163,7 @@ export const FullSearch = observer(() => {
toNode({el: r.el, file: s.file})} - className={'cursor-default dark:hover:text-gray-200 hover:text-gray-800 group break-all'} + className={'cursor-default dark:hover:text-gray-200 hover:text-gray-800 group break-all ellipsis-10'} dangerouslySetInnerHTML={{__html: r.text}} /> )} diff --git a/src/renderer/src/components/Home.tsx b/src/renderer/src/components/Home.tsx index e14da4ab..12657d42 100644 --- a/src/renderer/src/components/Home.tsx +++ b/src/renderer/src/components/Home.tsx @@ -12,6 +12,7 @@ import {Characters} from './Characters' import {db} from '../store/db' import {QuickOpen} from './QuickOpen' import {action} from 'mobx' +import {exportHtml} from '../editor/output/html' export const Home = observer(() => { const initial = useCallback(async () => { @@ -53,6 +54,10 @@ 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) + } const clearRecent = () => { db.recent.clear() } @@ -60,15 +65,16 @@ export const Home = observer(() => { window.electron.ipcRenderer.on('open', open) window.electron.ipcRenderer.on('create', create) window.electron.ipcRenderer.on('call-print-pdf', printPdf) + window.electron.ipcRenderer.on('call-print-html', printHtml) window.electron.ipcRenderer.on('clear-recent', clearRecent) 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('call-print-html', printHtml) window.electron.ipcRenderer.removeListener('clear-recent', clearRecent) } }, []) - const moveStart = useCallback((e: React.MouseEvent) => { const left = e.clientX const startWidth = treeStore.width @@ -86,6 +92,11 @@ export const Home = observer(() => { localStorage.setItem('tree-width', String(treeStore.width)) }, {once: true}) }, []) + + useEffect(() => { + window.electron.ipcRenderer.send('open-file', treeStore.openNote && treeStore.openNote.ext === 'md') + }, [treeStore.openNote]) + return (
diff --git a/src/renderer/src/components/Nav.tsx b/src/renderer/src/components/Nav.tsx index b0af7f01..164f868d 100644 --- a/src/renderer/src/components/Nav.tsx +++ b/src/renderer/src/components/Nav.tsx @@ -66,6 +66,7 @@ export const Nav = observer(() => {
+ {/**/}
{ className={'text-lg duration-200 dark:group-hover:text-gray-300 group-hover:text-gray-700'} />
+ {/**/}
diff --git a/src/renderer/src/editor/EditorFrame.tsx b/src/renderer/src/editor/EditorFrame.tsx index 8d8eb68d..0f8fd02c 100644 --- a/src/renderer/src/editor/EditorFrame.tsx +++ b/src/renderer/src/editor/EditorFrame.tsx @@ -21,6 +21,7 @@ import {getImageData} from '../utils' import {configStore} from '../store/config' import isHotkey from 'is-hotkey' import {isMod} from '../utils/keyboard' +import {InsertNetworkImage} from './tools/InsertNetworkImage' export const EditorFrame = observer(({tab}: { tab: Tab }) => { @@ -130,6 +131,7 @@ export const EditorFrame = observer(({tab}: { + ) diff --git a/src/renderer/src/editor/output/html.ts b/src/renderer/src/editor/output/html.ts index 8d3ae49d..794ddfd1 100644 --- a/src/renderer/src/editor/output/html.ts +++ b/src/renderer/src/editor/output/html.ts @@ -1,40 +1,12 @@ import mermaid from 'mermaid' import {Node} from 'slate' -import {configStore} from '../../store/config' import {ipcRenderer} from 'electron' import {isAbsolute, join, parse, sep} from 'path' import {treeStore} from '../../store/tree' import {IFileItem} from '../../index' import {MainApi, saveDialog} from '../../api/main' import katex from 'katex' -import {getHeadId, getSectionTexts} from '../../utils/sections' -import {nanoid} from 'nanoid' -import {message$} from '../../utils' - -interface ChapterItem { - folder: boolean - path?: string - name: string - children?: ChapterItem[] -} - -interface DocMap { - name: string - id: string - title?: string - folder?: boolean - content?: string, - outline?: string - children?: DocMap[] -} -interface EbookConfig { - name: string - id?: number - strategy: 'auto' | 'custom' - ignorePaths?: string - map?: any -} -let currentSections: any[] = [] +import {findText, getHeadId} from '../../utils/sections' const icon = 'data:image/png;base64,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' const exist = async (path: string) => { @@ -45,40 +17,6 @@ const exist = async (path: string) => { return false } } -const getMapByAuto = async (nodes: IFileItem[], ignorePath: string[] = []) => { - let docs: DocMap[] = [] - for (let n of nodes) { - if ( - ignorePath.some(p => n.filePath.startsWith(p)) || - n.filePath.split(sep).some(p => p.startsWith('.')) - ) continue - if (n.folder) { - docs.push({ - name: n.filename, - folder: true, - id: 'a' + window.api.md5(n.filePath), - children: await getMapByAuto(n.children!, ignorePath) - }) - } else { - const tree = treeStore.schemaMap.get(n)?.state || [] - const title = parse(n.filePath).name - const id = 'a' + window.api.md5(n.filePath) - docs.push({ - name: n.filename, - id, - title: title, - content: await transform(tree, n), - outline: getOutline(tree) - }) - currentSections.push({ - section: getSectionTexts(tree).sections, - path: id, - name: n.filename - }) - } - } - return docs -} const getAssets = async () => { const env = await ipcRenderer.invoke('get-env') as {webPath: string} const fs = window.api.fs @@ -90,176 +28,6 @@ const getAssets = async () => { css: await fs.readFile(join(env.webPath, cssPath!), {encoding:'utf-8'}) } } -const eBookTemplate = async (data: { - map: any, - name: string -}) => { - const {script, css} = await getAssets() - return ` - - - - - - ${data.name} - - - -
-
-
${data.name} -
- - - -
-
-
-
- -
-
-
-
-
-
On this page
-
-
-
-
- - - - - ` -} - -const getChapterByConfig = async (ctx: { - items: ChapterItem[], - schemaMap: Map - config: EbookConfig -}) => { - const chapters: DocMap[] = [] - for (let c of ctx.items) { - if (!c.name) throw new Error(configStore.isZh ? 'name字段为空' : 'name field is empty') - if (c.folder) { - if (!c.children?.length) continue - chapters.push({ - ...c, - id: 'a' + window.api.md5(nanoid()), - children: await getChapterByConfig({ - ...ctx, - items: c.children! - }) - }) - } else { - if (!c.path) throw new Error(configStore.isZh ? 'path字段为空' : 'path fields empty') - let realPath = join(treeStore.root.filePath!, c.path!) - if (!realPath.endsWith('.md')) realPath += '.md' - if (!(await exist(realPath))) throw new Error(`${c.path} ${configStore.isZh ? '文件不存在' : 'file dose not exist'}`) - const path = window.api.md5(realPath) - const item = ctx.schemaMap.get(realPath) - if (!item) continue - const content = await transform(item.schema, item.item) - const outline = getOutline(item.schema) - const id = 'a' + path - const chapter: DocMap = { - folder: false, - name: c.name, - id, - content, - outline - } - currentSections.push({ - section: getSectionTexts(item.schema).sections, - path: id, - name: c.name - }) - chapters.push(chapter) - } - } - return chapters -} -export const exportEbookHtml = async (config: EbookConfig) => { - currentSections = [] - if (!treeStore.root) return null - let map:any - if (config.strategy === 'auto') { - map = await getMapByAuto(treeStore.root.children!, config.ignorePaths ? config.ignorePaths.split(',') : undefined) - } - if (config.strategy === 'custom') { - const noteSchemaMap = new Map() - const stack = treeStore.root.children!.slice() - while (stack.length) { - const item = stack.shift()! - if (item.ext === 'md') { - noteSchemaMap.set(item.filePath, { - schema: treeStore.schemaMap.get(item)?.state || [], - item: item - }) - } - if (item.folder) { - stack.push(...item.children!) - } - } - try { - map = await getChapterByConfig({ - items: JSON.parse(config.map), - schemaMap: noteSchemaMap, - config: config - }) - } catch (e) { - if (e instanceof Error) { - message$.next({ - type: 'error', - content: e.message || '导出失败' - }) - } - } - } - - const html = await eBookTemplate({ - map: {data: map, sections: currentSections}, name: config.name - }) - const save = await saveDialog({ - filters: [{name: 'html', extensions: ['html']}] - }) - if (save.filePath) { - await window.api.fs.writeFile(save.filePath, html, {encoding: 'utf-8'}) - MainApi.openInFolder(save.filePath) - } - return '' -} export const exportHtml = async (node: IFileItem) => { const tree = treeStore.schemaMap.get(node)?.state || [] @@ -278,22 +46,21 @@ export const exportHtml = async (node: IFileItem) => {
-
${title} -
- @@ -326,48 +95,52 @@ export const exportHtml = async (node: IFileItem) => { const getOutline = (schema: any[]) => { const list = schema.filter(n => n.level > 1 && n.level < 5).map(h => { const id = getHeadId(h) - return `` + return `${findText(h)}` }) if (list.length) return list.join('') return '' } -const transform = async (schema: any[], node: IFileItem) => { +const transform = async (schema: any[], node: IFileItem, path:number[] = []) => { let str = '' - for (let e of schema) { + for (let i = 0; i < schema.length; i++) { + const e = schema[i] + const next = [...path, i] + const nextPath = next.join('-') switch (e.type) { case 'head': const id = getHeadId(e) - str += `${await transform(e.children || [], node)}` + str += `${await transform(e.children || [], node, next)}` break case 'paragraph': - str += `

${await transform(e.children || [], node)}

` + str += `

${await transform(e.children || [], node, next)}

` break case 'blockquote': - str += `
${await transform(e.children || [], node)}
` + str += `
${await transform(e.children || [], node, next)}
` break case 'hr': str += '
' break case 'list': let tag = e.order ? 'ol' : 'ul' - str += `<${tag} class="m-list">${await transform(e.children || [], node)}` + str += `<${tag} class="m-list">${await transform(e.children || [], node, next)}` break case 'list-item': const task = typeof e.checked === 'boolean' - str += `
  • ${task ? `` : ''}${await transform(e.children || [], node)}
  • ` + str += `
  • ${task ? `` : ''}${await transform(e.children || [], node, next)}
  • ` break case 'table': let head = '' - for (let h of e.children[0].children) { - head += `${await transform(h.children || [], node)}` + for (let j = 0; j < e.children[0].children.length; j++) { + const h = e.children[0].children[j] + head += `${await transform(h.children || [], node, next)}` } - str += `${head}${await transform(e.children?.slice(1) || [], node)}
    ` + str += `${head}${await transform(e.children?.slice(1) || [], node, next)}
    ` break case 'table-row': - str += `${await transform(e.children, node)}` + str += `${await transform(e.children, node, next)}` break case 'table-cell': - str += `${await transform(e.children, node)}` + str += `${await transform(e.children, node, next)}` break case 'media': let url = e.url @@ -404,11 +177,18 @@ const transform = async (schema: any[], node: IFileItem) => { 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 += `
    -
    ${e.language}
    -
    -${codeHtml}
    -
    ` + str += ` +
    +
    +
    + ${e.language || ''} +
    +
    +
    ${codeHtml}
    +
    + ` } break } diff --git a/src/renderer/src/editor/store.ts b/src/renderer/src/editor/store.ts index 76528b0d..e2160727 100644 --- a/src/renderer/src/editor/store.ts +++ b/src/renderer/src/editor/store.ts @@ -30,6 +30,7 @@ export class EditorStore { words: 0, characters: 0 } + openInsertNetworkImage = false webview = false sel: BaseSelection | undefined focus = false @@ -72,7 +73,6 @@ export class EditorStore { this.matchCount = 0 }) } - hideRanges() { if (this.highlightCache.size) { setTimeout(() => { diff --git a/src/renderer/src/editor/tools/InsertNetworkImage.tsx b/src/renderer/src/editor/tools/InsertNetworkImage.tsx new file mode 100644 index 00000000..e9948159 --- /dev/null +++ b/src/renderer/src/editor/tools/InsertNetworkImage.tsx @@ -0,0 +1,52 @@ +import {observer} from 'mobx-react-lite' +import {Button, Input, Modal} from 'antd' +import {useEditorStore} from '../store' +import {action, runInAction} from 'mobx' +import {useLocalState} from '../../hooks/useLocalState' +import {ReactEditor} from 'slate-react' +import isHotkey from 'is-hotkey' +import {useCallback} from 'react' + +export const InsertNetworkImage = observer(() => { + const store = useEditorStore() + const [state, setState] = useLocalState({ + url: '' + }) + const insert = useCallback(() => { + runInAction(() => { + store.openInsertNetworkImage = false + }) + ReactEditor.focus(store.editor) + setTimeout(() => { + store.insertInlineNode(state.url) + setState({url: ''}) + }) + }, []) + return ( + store.openInsertNetworkImage = false)} + > + { + if (isHotkey('enter', e)) { + insert() + } + }} + onChange={e => setState({url: e.target.value})} + /> + + + ) +}) diff --git a/src/renderer/src/share b/src/renderer/src/share index 856120a9..f34bbb22 160000 --- a/src/renderer/src/share +++ b/src/renderer/src/share @@ -1 +1 @@ -Subproject commit 856120a949a236e9195ab88a724e993af0c2385d +Subproject commit f34bbb22f860afa30e3dfeec2af12f5be1bf80b4 diff --git a/src/renderer/src/styles/color.scss b/src/renderer/src/styles/color.scss index 68a08f0c..3fcb9d84 100644 --- a/src/renderer/src/styles/color.scss +++ b/src/renderer/src/styles/color.scss @@ -3,7 +3,7 @@ --nav: #F9FBFE; --b1: rgb(229 231 235 / 0.8); --md-text: rgba(60,60,60,.95); - --tree-bg: #EFF1F4; + --tree-bg: #f4f5f6; --md-code-bg: #2a2929; --md-bg-mute: rgba(0, 0, 0, .07); --md-high: rgba(60,60,60,.3); diff --git a/src/renderer/src/styles/tailwind.scss b/src/renderer/src/styles/tailwind.scss index 4650abcb..45ceac64 100644 --- a/src/renderer/src/styles/tailwind.scss +++ b/src/renderer/src/styles/tailwind.scss @@ -52,3 +52,11 @@ .ant-btn-primary { background-color: #0ea5e9; } + +.ellipsis-10{ + overflow : hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 10; + -webkit-box-orient: vertical; +} diff --git a/src/renderer/src/utils/keyboard.ts b/src/renderer/src/utils/keyboard.ts index fca7914b..41d03c0b 100644 --- a/src/renderer/src/utils/keyboard.ts +++ b/src/renderer/src/utils/keyboard.ts @@ -4,6 +4,7 @@ import {EditorUtils} from '../editor/utils/editorUtils' import React from 'react' import isHotkey from 'is-hotkey' import {outputCache} from '../editor/output' +import {runInAction} from 'mobx' const formatList = (editor: Editor, node: NodeEntry, type: string) => { const isOrder = ['insertOrderedList', 'insertTaskOrderedList'].includes(type) @@ -26,6 +27,7 @@ const formatList = (editor: Editor, node: NodeEntry, type: string) => { })) for (let l of listItems) { + outputCache.delete(l[0]) Transforms.setNodes(editor, {checked: task ? l[0].checked || false : undefined}, {at: l[1]}) } } else { @@ -80,13 +82,21 @@ export class MenuKey { this.timer = window.setTimeout(() => { const sel = this.state?.editor.selection if (!this.state || !sel) return - if (task === 'insertImage' && sel && other) { + if (['insertNetworkImage', 'insertImage'].includes(task) && sel) { const [node] = Editor.nodes(this.state?.editor, { match: n => Element.isElement(n), mode: 'highest' }) if (node && node[0].type === 'code') return - this.state.insertInlineNode(other) + if (task === 'insertImage') { + this.state.insertInlineNode(other) + } else { + if (this.store.currentTab.current?.ext === 'md') { + runInAction(() => { + this.store.currentTab.store!.openInsertNetworkImage = true + }) + } + } return }