Skip to content

Commit

Permalink
feat: Complete custom sharing
Browse files Browse the repository at this point in the history
  • Loading branch information
1943time committed Oct 5, 2023
1 parent c33cc53 commit a0b16b2
Show file tree
Hide file tree
Showing 19 changed files with 415 additions and 147 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
token: ${{secrets.MD_TOKEN}}
submodules: true
- name: Setup Node.js environment
uses: actions/[email protected]
with:
Expand Down
3 changes: 0 additions & 3 deletions .gitmodules

This file was deleted.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Supports light and dark color theme. and generate your Markdown files into onlin
- Support exporting html and pdf.
- Can drag document elements to change their order.
- Supports pasting of HTML, markdown code, and plain text, when pasting HTML, it will automatically download the images in the HTML and convert the path.
- Non-intrusive sharing function, one-click to share a single markdown document or combine multiple markdown files into a combined document containing chapters, bluestone will automatically manage all dependencies.
- Through simple configuration, it is easy to synchronize the markdown documents generated by the editor to your own server or cloud storage, making it easy to quickly share documents with others.

## Drag
![](./docs/assets/drag.gif)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bluestone",
"version": "0.9.1",
"version": "1.3.5",
"description": "",
"main": "./out/main/index.js",
"license": "AGPL-3.0",
Expand Down
9 changes: 2 additions & 7 deletions src/main/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import icon from '../../resources/icon.png?asset'
export const baseUrl = is.dev && process.env['ELECTRON_RENDERER_URL'] ? process.env['ELECTRON_RENDERER_URL'] : join(__dirname, '../renderer/index.html')
const workerPath = join(__dirname, '../renderer/worker.html')
import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions
import {openAuth, listener} from './auth'

export const windowOptions: BrowserWindowConstructorOptions = {
show: false,
Expand Down Expand Up @@ -41,7 +40,7 @@ export const isDark = (config?: any) => {
const isBoolean = (v: any) => typeof v === 'boolean'

export const registerApi = () => {
listener(store)
store.delete('service-config')
ipcMain.on('to-worker', (e, ...args:any[]) => {
const window = BrowserWindow.fromWebContents(e.sender)!
window?.getBrowserView()?.webContents.send('task', ...args)
Expand All @@ -52,9 +51,6 @@ export const registerApi = () => {
ipcMain.handle('get-path', (e, type: Parameters<typeof app.getPath>[0]) => {
return app.getPath(type)
})
ipcMain.on('open-auth', (e, type: 'github') => {
openAuth(type)
})
ipcMain.handle('get-env', () => {
return {
isPackaged: app.isPackaged,
Expand Down Expand Up @@ -88,8 +84,7 @@ export const registerApi = () => {
mas: process.mas || false,
headingMarkLine: isBoolean(config.headingMarkLine) ? config.headingMarkLine : true,
dragToSort: isBoolean(config.dragToSort) ? config.dragToSort : true,
autoRebuild: isBoolean(config.autoRebuild) ? config.autoRebuild : true,
hideWebService: isBoolean(config.hideWebService) ? config.hideWebService : false
autoRebuild: isBoolean(config.autoRebuild) ? config.autoRebuild : true
}
})

Expand Down
3 changes: 1 addition & 2 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ type WinOptions = {
openFolder?: string
openFile?: string
}
console.log('path', app.getPath('userData'))
const windows = new Map<number, WinOptions>()
app.setAsDefaultProtocolClient('bluestone-markdown')
function createWindow(initial?: WinOptions): void {
Expand Down Expand Up @@ -126,7 +125,7 @@ app.whenReady().then(() => {
createWindow()
}
})
// console.log(app.getPath('userData'))

ipcMain.on('set-win', (e, data: WinOptions) => {
const window = BrowserWindow.fromWebContents(e.sender)!
if (!windows.get(window.id)) return
Expand Down
2 changes: 1 addition & 1 deletion src/preload/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {AliApi} from './sdk/ali'
import {Sdk} from './sdk'
import {Got} from 'got'
import {ExtendOptions} from 'got/dist/source/types'
import {Service} from './service'
import {Service} from './service/service'


declare global {
Expand Down
2 changes: 1 addition & 1 deletion src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const langSet = new Set(BUNDLED_LANGUAGES.map(l => [l.id, ...(l.aliases || [])])
let highlighter:Highlighter | null = null
import {toUnix} from 'upath'
import mime from 'mime-types'
import {Service} from './service'
import {Service} from './service/service'
let watchers = new Map<string, Watcher>()
let ready:any = null
const api = {
Expand Down
82 changes: 82 additions & 0 deletions src/preload/service/scriptService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {ipcRenderer} from 'electron'
import {join} from 'path'
import {readFileSync, writeFileSync} from 'fs'
import {Service} from './service'
import got from 'got'
import mimeTypes from 'mime-types'

interface Instance {
uploadDoc(ctx: {
json: object
randomName: string
filePath: string
htmlBuffer: Buffer
}): Promise<{name: string}>

removeFile(ctx: {name: string, filePath: string}): Promise<{success: true}>
}
export class ScriptService {
instance: null | Instance = null
constructor(
readonly service: Service
) {
this.initial()
}
initial() {
this.service.getConfig().then(async res => {
if (res?.type === 'custom') {
const path = await this.getScriptPath()
delete require.cache[require.resolve(path)]
const script = require(path)
this.instance = new script.Service({
got, mimeTypes
})
}
})
}
private async getScriptPath() {
const dataPath = await ipcRenderer.invoke('get-path', 'userData') as string
return join(dataPath, 'bsService.js')
}
async saveScript(script: string, domain: string) {
const path = await this.getScriptPath()
writeFileSync(path, script)
await this.test(path, domain)
}
async test(path: string, domain: string) {
delete require.cache[require.resolve(path)]
const script = require(path)
if (!script?.Service) throw new Error('Please export the Service instance in the script')
const service = new script.Service({
got, mimeTypes
})
if (!service.uploadDependencyLibrary) throw new Error('Please implement the uploadDependencyLibrary method')
if (!service.uploadDoc) throw new Error('Please implement the uploadDoc method')
if (!service.removeFile) throw new Error('Please implement the removeFile method')
const assets = await this.service.getAssets()
await service.uploadDependencyLibrary('lib/script.js', readFileSync(assets.script))
await service.uploadDependencyLibrary('lib/favicon.png', readFileSync(assets.script))
await service.uploadDependencyLibrary('lib/style.css', readFileSync(assets.css))
await service.uploadDependencyLibrary('lib/katex.min.css', readFileSync(assets.katex))
const res = await service.uploadDoc({
json: [{text: ' '}],
randomName: 'doc/test.html',
filePath: 'user/test.md',
htmlBuffer: Buffer.from(' ', 'utf-8')
})
if (!res?.name) throw new Error('the method uploadDoc must return the format {name: string}')
await got.get(domain + '/' + 'doc/test.html').catch(e => {
throw new Error(`url ${domain}/doc/text.html is not accessible`)
})
await service.removeFile({
name: 'doc/test.html',
filePath: 'user/test.md'
})
await got.get(domain + '/' + 'doc/test.html').then(e => {
throw new Error(`invalid`)
}).catch((e: any) => {
if (e.message === 'invalid') throw new Error('Invalid deletion of doc/text.html, please check the removeFile method')
return null
})
}
}
58 changes: 39 additions & 19 deletions src/preload/service.ts → src/preload/service/service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import {NodeSSH} from 'node-ssh'
import {join, sep} from 'path'
import {ipcRenderer} from 'electron'
import {toUnix} from 'upath'
const ssh = new NodeSSH()
import {readdirSync, readFileSync} from 'fs'
import {SFTPWrapper} from 'ssh2'
import {ScriptService} from './scriptService'
const unixJoin = (...args:string[]) => toUnix(join(...args))
export class Service {
ssh: NodeSSH | null = null
private async getAssets() {
script: ScriptService
constructor() {
this.script = new ScriptService(this)
}
async getAssets() {
const env = await ipcRenderer.invoke('get-env') as {webPath: string}
const files = readdirSync(env.webPath)
const scriptPath = files.find(f => f.endsWith('.js'))
const cssPath = files.find(f => f === 'style.css')
return {
script: await readFileSync(join(env.webPath, scriptPath!), {encoding:'utf-8'}),
icon: join(env.webPath, 'favicon.png'),
katex: join(env.webPath, 'katex.min.css'),
css: await readFileSync(join(env.webPath, cssPath!), {encoding:'utf-8'})
script: unixJoin(env.webPath, scriptPath!),
icon: unixJoin(env.webPath, 'favicon.png'),
katex: unixJoin(env.webPath, 'katex.min.css'),
css: unixJoin(env.webPath, cssPath!)
}
}
close() {
Expand All @@ -24,7 +31,7 @@ export class Service {
this.ssh = null
}
}
private async getConfig() {
async getConfig() {
return ipcRenderer.invoke('get-service-config') as Record<any, any>
}
private remoteExists(sftp: SFTPWrapper, filePath: string) {
Expand Down Expand Up @@ -52,14 +59,14 @@ export class Service {
})
const assets = await this.getAssets()
await this.ssh.withSFTP(async sftp => {
const lib = join(config.target, 'lib')
const lib = unixJoin(config.target, 'lib')
const exist = await this.remoteExists(sftp, lib)
if (!exist) await this.ssh!.mkdir(lib)
await this.writeFileBySsh(sftp, join(lib, 'style.css'), assets.css)
await this.writeFileBySsh(sftp, join(lib, 'script.js'), assets.script)
})
await this.uploadFile('katex.min.css', assets.katex, 'lib')
await this.uploadFile('favicon.png', assets.icon, 'lib')
await this.uploadFile('style.css', assets.css, 'lib')
await this.uploadFile('script.js', assets.script, 'lib')
this.close()
}
private writeFileBySsh(sftp: SFTPWrapper, path: string, content: string) {
Expand All @@ -73,39 +80,52 @@ export class Service {
})
})
}
async uploadDoc(name: string, content: string) {
async uploadDoc(data: {
name: string, content: string, json: object, filePath: string
}) {
const config = await this.getConfig()
if (config.type === 'ssh') {
if (!this.ssh) this.ssh = await this.connectSsh(config)
await this.ssh.withSFTP(async sftp => {
const doc = join(config.target, 'doc')
const doc = unixJoin(config.target, 'doc')
const exist = await this.remoteExists(sftp, doc)
if (!exist) await this.ssh!.mkdir(doc)
await this.writeFileBySsh(sftp, join(config.target, name), content)
await this.writeFileBySsh(sftp, unixJoin(config.target, data.name), data.content)
})
this.close()
return {name: data.name}
} else {
return this.script.instance?.uploadDoc({
filePath: data.filePath,
json: data.json,
randomName: data.name,
htmlBuffer: Buffer.from(data.content, 'utf-8')
})
}
}
async deleteDoc(path: string) {
async deleteDoc(name: string, filePath: string) {
const config = await this.getConfig()
if (config.type === 'ssh') {
if (!this.ssh) this.ssh = await this.connectSsh(config)
await this.ssh!.execCommand(`rm -f ${join(config.target, path)}`)
await this.ssh!.execCommand(`rm -f ${unixJoin(config.target, name)}`)
this.close()
} else {
await this.script.instance?.removeFile({
name,
filePath
})
}
this.close()
}
async uploadFile(name: string, filePath: string, dir = 'assets'): Promise<any> {
const config = await this.getConfig()
if (config.type === 'ssh') {
if (!this.ssh) this.ssh = await this.connectSsh(config)
await this.ssh.withSFTP(async sftp => {
const assets = join(config.target, dir)
const assets = unixJoin(config.target, dir)
const exist = await this.remoteExists(sftp, assets)
if (!exist) await this.ssh!.mkdir(assets)
})
await this.ssh!.putFile(filePath, join(config.target, dir, name))
} else {

await this.ssh!.putFile(filePath, unixJoin(config.target, dir, name))
}
}
}
12 changes: 9 additions & 3 deletions src/renderer/src/components/AceCode.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import AceEditor from 'react-ace'
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/mode-javascript.js";
import "ace-builds/src-noconflict/theme-cloud9_night";
import "ace-builds/src-noconflict/theme-cloud9_day";
import {configStore} from '../store/config'
export function AceCode(props: {
value?: string
mode: 'json' | 'javascript'
height?: string
onChange?: (v: string) => void
}) {
return (
<div className={'pt-2'}>
<AceEditor
mode={'json'}
mode={props.mode}
setOptions={{
showGutter: false,
highlightActiveLine: false,
useWorker: false
}}
theme={configStore.config.dark ? 'cloud9_night' : 'cloud9_day'}
height={'400px'}
onLoad={editor => {
editor.renderer.setPadding(10)
editor.renderer.setScrollMargin(10, 0, 0, 0)
}}
height={props.height || '400px'}
width={'100%'}
tabSize={2}
value={props.value}
Expand Down
3 changes: 0 additions & 3 deletions src/renderer/src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import {Fragment, useMemo} from 'react'
import {MainApi} from '../api/main'
import {Update} from './Update'
import {isMac, isWindows} from '../utils'
import {Share} from '../share/Share'
import {User} from '../share/User'
import {configStore} from '../store/config'
import {Server} from '../server/Server'
export const Nav = observer(() => {
const paths = useMemo(() => {
Expand Down
13 changes: 0 additions & 13 deletions src/renderer/src/components/Set.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,6 @@ export const Set = observer(() => {
<Checkbox checked={configStore.config.autoRebuild} onChange={e => configStore.setConfig('autoRebuild', e.target.checked)}/>
</div>
</div>
<div className={'flex justify-between items-center py-3'}>
<div className={'text-sm'}>
<span className={'mr-1'}>Hide Web Services</span> <Help text={(
<>
If you need to share documents with others, web services can share markdown documents online in a minimalist way,
<a className={'link ml-1'} href={'https://pb.bluemd.me/official/book/docs/share'} target={'_blank'}>more</a>.
</>
)}/>
</div>
<div>
<Checkbox checked={configStore.config.hideWebService} onChange={e => configStore.setConfig('hideWebService', e.target.checked)}/>
</div>
</div>
<div className={'flex justify-between items-center py-3'}>
<div className={'text-sm'}>
Heading Mark Line
Expand Down
1 change: 0 additions & 1 deletion src/renderer/src/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {MElement, MLeaf} from './elements'
import {codeCache, SetNodeToDecorations, useHighlight} from './plugins/useHighlight'
import {useKeyboard} from './plugins/useKeyboard'
import {useOnchange} from './plugins/useOnchange'
import './parser'
import {htmlParser} from './plugins/htmlParser'
import {observer} from 'mobx-react-lite'
import {IFileItem} from '../index'
Expand Down
Loading

0 comments on commit a0b16b2

Please sign in to comment.