mirror of
https://github.com/heyman/heynote.git
synced 2024-11-29 03:13:48 +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
|
import { isMac } from "./detect-platform.js"
|
||||||
kuk∞∞∞javascript
|
|
||||||
oj∞∞∞prutt
|
|
||||||
hej∞∞∞python
|
|
||||||
f = lambda: 2 +1`;*/
|
|
||||||
|
|
||||||
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
|
∞∞∞text-a
|
||||||
Welcome to Heynote!
|
Welcome to Heynote!
|
||||||
|
|
||||||
@ -98,4 +109,4 @@ Shopping list:
|
|||||||
- Milk
|
- Milk
|
||||||
- Eggs
|
- Eggs
|
||||||
- Bread
|
- Bread
|
||||||
- Cheese`;
|
- Cheese`
|
@ -1,7 +1,10 @@
|
|||||||
import { app, BrowserWindow, shell, ipcMain, Menu, nativeTheme } from 'electron'
|
import { app, BrowserWindow, shell, ipcMain, Menu, nativeTheme } from 'electron'
|
||||||
import { release } from 'node:os'
|
import { release } from 'node:os'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
|
import * as jetpack from "fs-jetpack";
|
||||||
import menu from './menu'
|
import menu from './menu'
|
||||||
|
import { initialContent, initialDevContent } from '../initial-content'
|
||||||
|
import { WINDOW_CLOSE_EVENT } from '../constants';
|
||||||
|
|
||||||
// The built directory structure
|
// The built directory structure
|
||||||
//
|
//
|
||||||
@ -43,6 +46,9 @@ let win: BrowserWindow | null = null
|
|||||||
const preload = join(__dirname, '../preload/index.js')
|
const preload = join(__dirname, '../preload/index.js')
|
||||||
const url = process.env.VITE_DEV_SERVER_URL
|
const url = process.env.VITE_DEV_SERVER_URL
|
||||||
const indexHtml = join(process.env.DIST, 'index.html')
|
const indexHtml = join(process.env.DIST, 'index.html')
|
||||||
|
const isDev = !!process.env.VITE_DEV_SERVER_URL
|
||||||
|
let contentSaved = false
|
||||||
|
|
||||||
|
|
||||||
async function createWindow() {
|
async function createWindow() {
|
||||||
win = new BrowserWindow({
|
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"
|
//nativeTheme.themeSource = "light"
|
||||||
|
|
||||||
if (process.env.VITE_DEV_SERVER_URL) { // electron-vite-vue#298
|
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) => {
|
ipcMain.handle('dark-mode:set', (event, mode) => {
|
||||||
nativeTheme.themeSource = mode
|
nativeTheme.themeSource = mode
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('dark-mode:get', () => nativeTheme.themeSource)
|
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')
|
const { contextBridge } = require('electron')
|
||||||
import darkMode from "./theme-mode"
|
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", {
|
contextBridge.exposeInMainWorld("platform", {
|
||||||
isMac,
|
isMac,
|
||||||
@ -9,6 +11,31 @@ contextBridge.exposeInMainWorld("platform", {
|
|||||||
})
|
})
|
||||||
contextBridge.exposeInMainWorld('darkMode', darkMode)
|
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']) {
|
function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
|
||||||
return new Promise((resolve) => {
|
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",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
|
"debounce": "^1.2.1",
|
||||||
"electron": "^22.0.2",
|
"electron": "^22.0.2",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^23.6.0",
|
||||||
|
"fs-jetpack": "^5.1.0",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^4.0.3",
|
"vite": "^4.0.3",
|
||||||
@ -2192,6 +2194,12 @@
|
|||||||
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
|
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
@ -2830,6 +2838,36 @@
|
|||||||
"node": ">=6 <7 || >=8"
|
"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": {
|
"node_modules/fs-minipass": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||||
|
@ -47,8 +47,10 @@
|
|||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
|
"debounce": "^1.2.1",
|
||||||
"electron": "^22.0.2",
|
"electron": "^22.0.2",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^23.6.0",
|
||||||
|
"fs-jetpack": "^5.1.0",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^4.0.3",
|
"vite": "^4.0.3",
|
||||||
|
@ -1,22 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { ref, shallowRef } from 'vue'
|
|
||||||
import { HeynoteEditor } from '../editor/editor.js'
|
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 {
|
export default {
|
||||||
props: [
|
props: [
|
||||||
@ -37,10 +20,20 @@ Welcome to Heynote!
|
|||||||
this.$emit("openLanguageSelector")
|
this.$emit("openLanguageSelector")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// load buffer content and create editor
|
||||||
|
window.heynote.buffer.load().then((content) => {
|
||||||
this.editor = new HeynoteEditor({
|
this.editor = new HeynoteEditor({
|
||||||
element: this.$refs.editor,
|
element: this.$refs.editor,
|
||||||
content: this.development ? testContent : initialContent,
|
content: content,
|
||||||
theme: this.theme,
|
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 { changeCurrentBlockLanguage } from "./block/commands.js"
|
||||||
import { heynoteKeymap } from "./keymap.js"
|
import { heynoteKeymap } from "./keymap.js"
|
||||||
import { languageDetection } from "./language-detection/autodetect.js"
|
import { languageDetection } from "./language-detection/autodetect.js"
|
||||||
|
import { autoSaveContent } from "./save.js"
|
||||||
|
|
||||||
|
|
||||||
export class HeynoteEditor {
|
export class HeynoteEditor {
|
||||||
constructor({element, content, focus=true, theme="light"}) {
|
constructor({element, content, focus=true, theme="light", saveFunction=null}) {
|
||||||
this.element = element
|
this.element = element
|
||||||
this.theme = new Compartment
|
this.theme = new Compartment
|
||||||
|
|
||||||
@ -43,6 +44,8 @@ export class HeynoteEditor {
|
|||||||
EditorView.editorAttributes.of((view) => {
|
EditorView.editorAttributes.of((view) => {
|
||||||
return {class: view.state.facet(EditorView.darkTheme) ? "dark-theme" : "light-theme"}
|
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) {
|
setCurrentLanguage(lang, auto=false) {
|
||||||
changeCurrentBlockLanguage(this.view.state, this.view.dispatch, lang, auto)
|
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