mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 05:59:53 +08:00
Merge e5db7de9ea
into 11c8cc1e19
This commit is contained in:
commit
c2b5e5c9fe
@ -86,7 +86,8 @@
|
|||||||
"vue-electron": "^1.0.6",
|
"vue-electron": "^1.0.6",
|
||||||
"vue-router": "^3.5.3",
|
"vue-router": "^3.5.3",
|
||||||
"vuex": "^3.6.2",
|
"vuex": "^3.6.2",
|
||||||
"webfontloader": "^1.6.28"
|
"webfontloader": "^1.6.28",
|
||||||
|
"web3.storage": "latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.17.9",
|
"@babel/core": "^7.17.9",
|
||||||
|
@ -3,6 +3,7 @@ import EventEmitter from 'events'
|
|||||||
import log from 'electron-log'
|
import log from 'electron-log'
|
||||||
import Watcher, { WATCHER_STABILITY_THRESHOLD, WATCHER_STABILITY_POLL_INTERVAL } from '../filesystem/watcher'
|
import Watcher, { WATCHER_STABILITY_THRESHOLD, WATCHER_STABILITY_POLL_INTERVAL } from '../filesystem/watcher'
|
||||||
import { WindowType } from '../windows/base'
|
import { WindowType } from '../windows/base'
|
||||||
|
import { Web3Storage, getFilesFromPath } from 'web3.storage'
|
||||||
|
|
||||||
class WindowActivityList {
|
class WindowActivityList {
|
||||||
constructor () {
|
constructor () {
|
||||||
@ -424,6 +425,16 @@ class WindowManager extends EventEmitter {
|
|||||||
this._watcher.ignoreChangedEvent(windowId, pathname, duration)
|
this._watcher.ignoreChangedEvent(windowId, pathname, duration)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.on('add-file-to-ipfs', async (pathname) => {
|
||||||
|
// A changed event is emitted earliest after the stability threshold.
|
||||||
|
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6ZXRocjoweDU4ZDc1ZjYzN2Y5NDc2YzVkQmU1OGIxNzEyN0Q1MGU0NDgxMzUzQjQiLCJpc3MiOiJ3ZWIzLXN0b3JhZ2UiLCJpYXQiOjE2NjE0MDU2Mzc2MDQsIm5hbWUiOiJ4aW5taW5zdSJ9.sb1ATMTwOtsquSn6kTWQylCRUZjVDWrGUq5o6sLHlis'
|
||||||
|
const storage = new Web3Storage({ token })
|
||||||
|
|
||||||
|
const files = await getFilesFromPath(pathname)
|
||||||
|
const cid = await storage.put(files)
|
||||||
|
console.log('Content added with CID:', cid)
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.on('window-close-by-id', id => {
|
ipcMain.on('window-close-by-id', id => {
|
||||||
this.forceCloseById(id)
|
this.forceCloseById(id)
|
||||||
})
|
})
|
||||||
|
@ -30,3 +30,12 @@ export const writeFile = (pathname, content, extension, options = 'utf-8') => {
|
|||||||
|
|
||||||
return fs.outputFile(pathname, content, options)
|
return fs.outputFile(pathname, content, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const writeFileToIpfs = async (pathname, content, extension, options = 'utf-8') => {
|
||||||
|
if (!pathname) {
|
||||||
|
return Promise.reject(new Error('[ERROR] Cannot save file without path.'))
|
||||||
|
}
|
||||||
|
pathname = !extension || pathname.endsWith(extension) ? pathname : `${pathname}${extension}`
|
||||||
|
|
||||||
|
return fs.outputFile(pathname, content, options)
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ import iconv from 'iconv-lite'
|
|||||||
import { LINE_ENDING_REG, LF_LINE_ENDING_REG, CRLF_LINE_ENDING_REG } from '../config'
|
import { LINE_ENDING_REG, LF_LINE_ENDING_REG, CRLF_LINE_ENDING_REG } from '../config'
|
||||||
import { isDirectory2 } from 'common/filesystem'
|
import { isDirectory2 } from 'common/filesystem'
|
||||||
import { isMarkdownFile } from 'common/filesystem/paths'
|
import { isMarkdownFile } from 'common/filesystem/paths'
|
||||||
import { normalizeAndResolvePath, writeFile } from '../filesystem'
|
import { normalizeAndResolvePath, writeFile, writeFileToIpfs } from '../filesystem'
|
||||||
import { guessEncoding } from './encoding'
|
import { guessEncoding } from './encoding'
|
||||||
|
|
||||||
const getLineEnding = lineEnding => {
|
const getLineEnding = lineEnding => {
|
||||||
@ -67,6 +67,28 @@ export const writeMarkdownFile = (pathname, content, options) => {
|
|||||||
return writeFile(pathname, buffer, extension, undefined)
|
return writeFile(pathname, buffer, extension, undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the content file to ipfs.
|
||||||
|
*
|
||||||
|
* @param {string} pathname The path to the file.
|
||||||
|
* @param {string} content The buffer to save.
|
||||||
|
* @param {IMarkdownDocumentOptions} options The markdown document options
|
||||||
|
*/
|
||||||
|
export const writeMarkdownFileToIpfs = (pathname, content, options) => {
|
||||||
|
const { adjustLineEndingOnSave, lineEnding } = options
|
||||||
|
const { encoding, isBom } = options.encoding
|
||||||
|
const extension = path.extname(pathname) || '.md'
|
||||||
|
|
||||||
|
if (adjustLineEndingOnSave) {
|
||||||
|
content = convertLineEndings(content, lineEnding)
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = iconv.encode(content, encoding, { addBOM: isBom })
|
||||||
|
|
||||||
|
// TODO(@fxha): "safeSaveDocuments" using temporary file and rename syscall.
|
||||||
|
return writeFileToIpfs(pathname, buffer, extension, undefined)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the contents of a markdown file.
|
* Reads the contents of a markdown file.
|
||||||
*
|
*
|
||||||
|
@ -9,7 +9,7 @@ import { showTabBar } from './view'
|
|||||||
import { COMMANDS } from '../../commands'
|
import { COMMANDS } from '../../commands'
|
||||||
import { EXTENSION_HASN, PANDOC_EXTENSIONS, URL_REG } from '../../config'
|
import { EXTENSION_HASN, PANDOC_EXTENSIONS, URL_REG } from '../../config'
|
||||||
import { normalizeAndResolvePath, writeFile } from '../../filesystem'
|
import { normalizeAndResolvePath, writeFile } from '../../filesystem'
|
||||||
import { writeMarkdownFile } from '../../filesystem/markdown'
|
import { writeMarkdownFile, writeMarkdownFileToIpfs } from '../../filesystem/markdown'
|
||||||
import { getPath, getRecommendTitleFromMarkdownString } from '../../utils'
|
import { getPath, getRecommendTitleFromMarkdownString } from '../../utils'
|
||||||
import pandoc from '../../utils/pandoc'
|
import pandoc from '../../utils/pandoc'
|
||||||
|
|
||||||
@ -158,6 +158,59 @@ const handleResponseForSave = async (e, { id, filename, markdown, pathname, opti
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleResponseForSaveToIpfs = async (e, { id, filename, markdown, pathname, options, defaultPath }) => {
|
||||||
|
const win = BrowserWindow.fromWebContents(e.sender)
|
||||||
|
let recommendFilename = getRecommendTitleFromMarkdownString(markdown)
|
||||||
|
if (!recommendFilename) {
|
||||||
|
recommendFilename = filename || 'Untitled'
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the file doesn't exist on disk add it to the recently used documents later
|
||||||
|
// and execute file from filesystem watcher for a short time. The file may exists
|
||||||
|
// on disk nevertheless but is already tracked by MarkText.
|
||||||
|
const alreadyExistOnDisk = !!pathname
|
||||||
|
|
||||||
|
let filePath = pathname
|
||||||
|
|
||||||
|
if (!filePath) {
|
||||||
|
const { filePath: dialogPath, canceled } = await dialog.showSaveDialog(win, {
|
||||||
|
defaultPath: path.join(defaultPath || getPath('documents'), `${recommendFilename}.md`)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (dialogPath && !canceled) {
|
||||||
|
filePath = dialogPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save dialog canceled by user - no error.
|
||||||
|
if (!filePath) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath = path.resolve(filePath)
|
||||||
|
const extension = path.extname(filePath) || '.md'
|
||||||
|
filePath = !filePath.endsWith(extension) ? filePath += extension : filePath
|
||||||
|
return writeMarkdownFileToIpfs(filePath, markdown, options, win)
|
||||||
|
.then(() => {
|
||||||
|
if (!alreadyExistOnDisk) {
|
||||||
|
ipcMain.emit('window-add-file-path', win.id, filePath)
|
||||||
|
ipcMain.emit('menu-add-recently-used', filePath)
|
||||||
|
|
||||||
|
const filename = path.basename(filePath)
|
||||||
|
win.webContents.send('mt::set-pathname', { id, pathname: filePath, filename })
|
||||||
|
} else {
|
||||||
|
ipcMain.emit('window-file-saved', win.id, filePath)
|
||||||
|
win.webContents.send('mt::tab-saved', id)
|
||||||
|
}
|
||||||
|
ipcMain.emit('add-file-to-ipfs', filePath)
|
||||||
|
return id
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
log.error('Error while saving:', err)
|
||||||
|
win.webContents.send('mt::tab-save-failure', id, err.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const showUnsavedFilesMessage = async (win, files) => {
|
const showUnsavedFilesMessage = async (win, files) => {
|
||||||
const { response } = await dialog.showMessageBox(win, {
|
const { response } = await dialog.showMessageBox(win, {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
@ -317,6 +370,8 @@ ipcMain.on('mt::close-window-confirm', async (e, unsavedFiles) => {
|
|||||||
|
|
||||||
ipcMain.on('mt::response-file-save', handleResponseForSave)
|
ipcMain.on('mt::response-file-save', handleResponseForSave)
|
||||||
|
|
||||||
|
ipcMain.on('mt::response-file-save-to-ipfs', handleResponseForSaveToIpfs)
|
||||||
|
|
||||||
ipcMain.on('mt::response-export', handleResponseForExport)
|
ipcMain.on('mt::response-export', handleResponseForExport)
|
||||||
|
|
||||||
ipcMain.on('mt::response-print', handleResponseForPrint)
|
ipcMain.on('mt::response-print', handleResponseForPrint)
|
||||||
@ -574,6 +629,12 @@ export const save = win => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const saveToIpfs = win => {
|
||||||
|
if (win && win.webContents) {
|
||||||
|
win.webContents.send('mt::editor-ask-file-save-to-ipfs')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const saveAs = win => {
|
export const saveAs = win => {
|
||||||
if (win && win.webContents) {
|
if (win && win.webContents) {
|
||||||
win.webContents.send('mt::editor-ask-file-save-as')
|
win.webContents.send('mt::editor-ask-file-save-as')
|
||||||
|
@ -33,6 +33,12 @@ export default function (keybindings, userPreference, recentlyUsedFiles) {
|
|||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.openFolder(browserWindow)
|
actions.openFolder(browserWindow)
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Open File From Ipfs',
|
||||||
|
accelerator: keybindings.getAccelerator('file.open-file'),
|
||||||
|
click (menuItem, browserWindow) {
|
||||||
|
actions.openFile(browserWindow)
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +93,12 @@ export default function (keybindings, userPreference, recentlyUsedFiles) {
|
|||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.saveAs(browserWindow)
|
actions.saveAs(browserWindow)
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Save to Ipfs',
|
||||||
|
accelerator: keybindings.getAccelerator('file.save'),
|
||||||
|
click (menuItem, browserWindow) {
|
||||||
|
actions.saveToIpfs(browserWindow)
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Auto Save',
|
label: 'Auto Save',
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
|
@ -11,6 +11,7 @@ const commandDescriptions = Object.freeze({
|
|||||||
'file.open-folder': 'File: Open Folder',
|
'file.open-folder': 'File: Open Folder',
|
||||||
'file.save': 'File: Save',
|
'file.save': 'File: Save',
|
||||||
'file.save-as': 'File: Save As...',
|
'file.save-as': 'File: Save As...',
|
||||||
|
'file.save-to-ipfs': 'File: Save to Ipfs...',
|
||||||
'file.move-file': 'File: Move...',
|
'file.move-file': 'File: Move...',
|
||||||
'file.rename-file': 'File: Rename...',
|
'file.rename-file': 'File: Rename...',
|
||||||
'file.quick-open': 'File: Show quick open dialog',
|
'file.quick-open': 'File: Show quick open dialog',
|
||||||
|
@ -63,6 +63,11 @@ const commands = [
|
|||||||
execute: async () => {
|
execute: async () => {
|
||||||
ipcRenderer.emit('mt::editor-ask-file-save', null)
|
ipcRenderer.emit('mt::editor-ask-file-save', null)
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
id: 'file.save-to-ipfs',
|
||||||
|
execute: async () => {
|
||||||
|
ipcRenderer.emit('mt::editor-ask-file-save-to-ipfs', null)
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
id: 'file.save-as',
|
id: 'file.save-as',
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
|
@ -144,6 +144,7 @@ export default {
|
|||||||
dispatch('LISTEN_FOR_SAVE_AS')
|
dispatch('LISTEN_FOR_SAVE_AS')
|
||||||
dispatch('LISTEN_FOR_MOVE_TO')
|
dispatch('LISTEN_FOR_MOVE_TO')
|
||||||
dispatch('LISTEN_FOR_SAVE')
|
dispatch('LISTEN_FOR_SAVE')
|
||||||
|
dispatch('LISTEN_FOR_SAVE_TO_IPFS')
|
||||||
dispatch('LISTEN_FOR_SET_PATHNAME')
|
dispatch('LISTEN_FOR_SET_PATHNAME')
|
||||||
dispatch('LISTEN_FOR_BOOTSTRAP_WINDOW')
|
dispatch('LISTEN_FOR_BOOTSTRAP_WINDOW')
|
||||||
dispatch('LISTEN_FOR_SAVE_CLOSE')
|
dispatch('LISTEN_FOR_SAVE_CLOSE')
|
||||||
|
@ -430,6 +430,24 @@ const actions = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
LISTEN_FOR_SAVE_TO_IPFS ({ state, rootState }) {
|
||||||
|
ipcRenderer.on('mt::editor-ask-file-save-to-ipfs', () => {
|
||||||
|
const { id, filename, pathname, markdown } = state.currentFile
|
||||||
|
const options = getOptionsFromState(state.currentFile)
|
||||||
|
const defaultPath = getRootFolderFromState(rootState)
|
||||||
|
if (id) {
|
||||||
|
ipcRenderer.send('mt::response-file-save-to-ipfs', {
|
||||||
|
id,
|
||||||
|
filename,
|
||||||
|
pathname,
|
||||||
|
markdown,
|
||||||
|
options,
|
||||||
|
defaultPath
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// need pass some data to main process when `save as` menu item clicked
|
// need pass some data to main process when `save as` menu item clicked
|
||||||
LISTEN_FOR_SAVE_AS ({ state, rootState }) {
|
LISTEN_FOR_SAVE_AS ({ state, rootState }) {
|
||||||
ipcRenderer.on('mt::editor-ask-file-save-as', () => {
|
ipcRenderer.on('mt::editor-ask-file-save-as', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user