mirror of
https://github.com/heyman/heynote.git
synced 2024-11-25 17:33:31 +01:00
Implement persistent buffer
This commit is contained in:
parent
c4e78a70ce
commit
14152fbede
1
electron/constants.ts
Normal file
1
electron/constants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const WINDOW_CLOSE_EVENT = "window-close"
|
@ -1,10 +1,21 @@
|
||||
/*export default `∞∞∞text
|
||||
kuk∞∞∞javascript
|
||||
oj∞∞∞prutt
|
||||
hej∞∞∞python
|
||||
f = lambda: 2 +1`;*/
|
||||
import { isMac } from "./detect-platform.js"
|
||||
|
||||
export default `
|
||||
const modChar = isMac ? "⌘" : "Ctrl"
|
||||
|
||||
export const initialContent = `
|
||||
∞∞∞text
|
||||
Welcome to Heynote!
|
||||
|
||||
[${modChar} + Enter] Insert new note block at cursor
|
||||
[${modChar} + L] Change block language
|
||||
[${modChar} + Down] Goto next block
|
||||
[${modChar} + Up] Goto previous block
|
||||
[${modChar} + A] Select all text in a note block. Press again to select the whole scratchpad
|
||||
[${modChar} + ⌥ + Up/Down] Add additional cursor above/below
|
||||
∞∞∞text-a
|
||||
`
|
||||
|
||||
export const initialDevContent = `
|
||||
∞∞∞text-a
|
||||
Welcome to Heynote!
|
||||
|
||||
@ -98,4 +109,4 @@ Shopping list:
|
||||
- Milk
|
||||
- Eggs
|
||||
- Bread
|
||||
- Cheese`;
|
||||
- Cheese`
|
@ -1,7 +1,10 @@
|
||||
import { app, BrowserWindow, shell, ipcMain, Menu, nativeTheme } from 'electron'
|
||||
import { release } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import * as jetpack from "fs-jetpack";
|
||||
import menu from './menu'
|
||||
import { initialContent, initialDevContent } from '../initial-content'
|
||||
import { WINDOW_CLOSE_EVENT } from '../constants';
|
||||
|
||||
// The built directory structure
|
||||
//
|
||||
@ -43,6 +46,9 @@ let win: BrowserWindow | null = null
|
||||
const preload = join(__dirname, '../preload/index.js')
|
||||
const url = process.env.VITE_DEV_SERVER_URL
|
||||
const indexHtml = join(process.env.DIST, 'index.html')
|
||||
const isDev = !!process.env.VITE_DEV_SERVER_URL
|
||||
let contentSaved = false
|
||||
|
||||
|
||||
async function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
@ -61,6 +67,15 @@ async function createWindow() {
|
||||
},
|
||||
})
|
||||
|
||||
win.on("close", (event) => {
|
||||
// Prevent the window from closing, and send a message to the renderer which will in turn
|
||||
// send a message to the main process to save the current buffer and close the window.
|
||||
if (!contentSaved) {
|
||||
event.preventDefault()
|
||||
}
|
||||
win?.webContents.send(WINDOW_CLOSE_EVENT)
|
||||
})
|
||||
|
||||
//nativeTheme.themeSource = "light"
|
||||
|
||||
if (process.env.VITE_DEV_SERVER_URL) { // electron-vite-vue#298
|
||||
@ -108,26 +123,35 @@ app.on('activate', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// New window example arg: new windows url
|
||||
ipcMain.handle('open-win', (_, arg) => {
|
||||
const childWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
preload,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
})
|
||||
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
childWindow.loadURL(`${url}#${arg}`)
|
||||
} else {
|
||||
childWindow.loadFile(indexHtml, { hash: arg })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
ipcMain.handle('dark-mode:set', (event, mode) => {
|
||||
nativeTheme.themeSource = mode
|
||||
})
|
||||
|
||||
ipcMain.handle('dark-mode:get', () => nativeTheme.themeSource)
|
||||
|
||||
const bufferPath = isDev ? join(app.getPath("userData"), "buffer-dev.txt") : join(app.getPath("userData"), "buffer.txt")
|
||||
|
||||
ipcMain.handle('buffer-content:load', async () => {
|
||||
if (jetpack.exists(bufferPath) === "file") {
|
||||
return await jetpack.read(bufferPath, 'utf8')
|
||||
} else {
|
||||
return isDev? initialDevContent : initialContent
|
||||
}
|
||||
});
|
||||
|
||||
async function save(content) {
|
||||
return await jetpack.write(bufferPath, content, {
|
||||
atomic: true,
|
||||
mode: '600',
|
||||
})
|
||||
}
|
||||
|
||||
ipcMain.handle('buffer-content:save', async (event, content) => {
|
||||
return await save(content)
|
||||
});
|
||||
|
||||
ipcMain.handle('buffer-content:saveAndQuit', async (event, content) => {
|
||||
await save(content)
|
||||
contentSaved = true
|
||||
app.quit()
|
||||
})
|
||||
|
@ -1,6 +1,8 @@
|
||||
const { contextBridge } = require('electron')
|
||||
import darkMode from "./theme-mode"
|
||||
import { isMac, isWindows, isLinux } from "./detect-platform"
|
||||
import { isMac, isWindows, isLinux } from "../detect-platform"
|
||||
import { ipcRenderer } from "electron"
|
||||
import { WINDOW_CLOSE_EVENT } from "../constants"
|
||||
|
||||
contextBridge.exposeInMainWorld("platform", {
|
||||
isMac,
|
||||
@ -9,6 +11,31 @@ contextBridge.exposeInMainWorld("platform", {
|
||||
})
|
||||
contextBridge.exposeInMainWorld('darkMode', darkMode)
|
||||
|
||||
contextBridge.exposeInMainWorld("heynote", {
|
||||
quit() {
|
||||
console.log("quitting")
|
||||
//ipcRenderer.invoke("app_quit")
|
||||
},
|
||||
|
||||
onWindowClose(callback) {
|
||||
ipcRenderer.on(WINDOW_CLOSE_EVENT, callback)
|
||||
},
|
||||
|
||||
buffer: {
|
||||
async load() {
|
||||
return await ipcRenderer.invoke("buffer-content:load")
|
||||
},
|
||||
|
||||
async save(content) {
|
||||
return await ipcRenderer.invoke("buffer-content:save", content)
|
||||
},
|
||||
|
||||
async saveAndQuit(content) {
|
||||
return await ipcRenderer.invoke("buffer-content:saveAndQuit", content)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
|
||||
return new Promise((resolve) => {
|
||||
|
38
package-lock.json
generated
38
package-lock.json
generated
@ -29,8 +29,10 @@
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"debounce": "^1.2.1",
|
||||
"electron": "^22.0.2",
|
||||
"electron-builder": "^23.6.0",
|
||||
"fs-jetpack": "^5.1.0",
|
||||
"sass": "^1.57.1",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.0.3",
|
||||
@ -2192,6 +2194,12 @@
|
||||
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/debounce": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
||||
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
@ -2830,6 +2838,36 @@
|
||||
"node": ">=6 <7 || >=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-jetpack": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-jetpack/-/fs-jetpack-5.1.0.tgz",
|
||||
"integrity": "sha512-Xn4fDhLydXkuzepZVsr02jakLlmoARPy+YWIclo4kh0GyNGUHnTqeH/w/qIsVn50dFxtp8otPL2t/HcPJBbxUA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimatch": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-jetpack/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-jetpack/node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
|
@ -47,8 +47,10 @@
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"debounce": "^1.2.1",
|
||||
"electron": "^22.0.2",
|
||||
"electron-builder": "^23.6.0",
|
||||
"fs-jetpack": "^5.1.0",
|
||||
"sass": "^1.57.1",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.0.3",
|
||||
|
@ -1,22 +1,5 @@
|
||||
<script>
|
||||
import { ref, shallowRef } from 'vue'
|
||||
import { HeynoteEditor } from '../editor/editor.js'
|
||||
import testContent from "../editor/fixture.js"
|
||||
|
||||
const modChar = window.platform.isMac ? "⌘" : "Ctrl"
|
||||
|
||||
const initialContent = `
|
||||
∞∞∞text
|
||||
Welcome to Heynote!
|
||||
|
||||
[${modChar} + Enter] Insert new note block at cursor
|
||||
[${modChar} + L] Change block language
|
||||
[${modChar} + Down] Goto next block
|
||||
[${modChar} + Up] Goto previous block
|
||||
[${modChar} + A] Select all text in a note block. Press again to select the whole scratchpad
|
||||
[${modChar} + ⌥ + Up/Down] Add additional cursor above/below
|
||||
∞∞∞text-a
|
||||
`
|
||||
|
||||
export default {
|
||||
props: [
|
||||
@ -37,10 +20,20 @@ Welcome to Heynote!
|
||||
this.$emit("openLanguageSelector")
|
||||
})
|
||||
|
||||
// load buffer content and create editor
|
||||
window.heynote.buffer.load().then((content) => {
|
||||
this.editor = new HeynoteEditor({
|
||||
element: this.$refs.editor,
|
||||
content: this.development ? testContent : initialContent,
|
||||
content: content,
|
||||
theme: this.theme,
|
||||
saveFunction: (content) => {
|
||||
window.heynote.buffer.save(content)
|
||||
},
|
||||
})
|
||||
})
|
||||
// set up window close handler that will save the buffer and quit
|
||||
window.heynote.onWindowClose(() => {
|
||||
window.heynote.buffer.saveAndQuit(this.editor.getContent())
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -11,10 +11,11 @@ import { noteBlockExtension } from "./block/block.js"
|
||||
import { changeCurrentBlockLanguage } from "./block/commands.js"
|
||||
import { heynoteKeymap } from "./keymap.js"
|
||||
import { languageDetection } from "./language-detection/autodetect.js"
|
||||
import { autoSaveContent } from "./save.js"
|
||||
|
||||
|
||||
export class HeynoteEditor {
|
||||
constructor({element, content, focus=true, theme="light"}) {
|
||||
constructor({element, content, focus=true, theme="light", saveFunction=null}) {
|
||||
this.element = element
|
||||
this.theme = new Compartment
|
||||
|
||||
@ -43,6 +44,8 @@ export class HeynoteEditor {
|
||||
EditorView.editorAttributes.of((view) => {
|
||||
return {class: view.state.facet(EditorView.darkTheme) ? "dark-theme" : "light-theme"}
|
||||
}),
|
||||
|
||||
saveFunction ? autoSaveContent(saveFunction, 2000) : [],
|
||||
],
|
||||
})
|
||||
|
||||
@ -77,6 +80,10 @@ export class HeynoteEditor {
|
||||
setCurrentLanguage(lang, auto=false) {
|
||||
changeCurrentBlockLanguage(this.view.state, this.view.dispatch, lang, auto)
|
||||
}
|
||||
|
||||
getContent() {
|
||||
return this.view.state.sliceDoc()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
20
src/editor/save.js
Normal file
20
src/editor/save.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { ViewPlugin } from "@codemirror/view"
|
||||
import { debounce } from "debounce"
|
||||
|
||||
|
||||
export const autoSaveContent = (saveFunction, interval) => {
|
||||
const save = debounce((view) => {
|
||||
//console.log("saving buffer")
|
||||
saveFunction(view.state.sliceDoc())
|
||||
}, interval);
|
||||
|
||||
return ViewPlugin.fromClass(
|
||||
class {
|
||||
update(update) {
|
||||
if (update.docChanged) {
|
||||
save(update.view)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user