mirror of
https://github.com/heyman/heynote.git
synced 2025-03-03 09:31:29 +01:00
WIP: Implement ability to create new notes.
Support cache of multiple Editor instances. Change so that current note name is included in the event data dispatched by emitCursorChange.
This commit is contained in:
parent
dbf675a28a
commit
4b39078689
@ -64,6 +64,14 @@ export class FileLibrary {
|
|||||||
return await this.files[path].save(content)
|
return await this.files[path].save(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async create(path, content) {
|
||||||
|
if (await this.exists(path)) {
|
||||||
|
throw new Error(`File already exists: ${path}`)
|
||||||
|
}
|
||||||
|
const fullPath = join(this.basePath, path)
|
||||||
|
await this.jetpack.writeAsync(fullPath, content)
|
||||||
|
}
|
||||||
|
|
||||||
async getList() {
|
async getList() {
|
||||||
console.log("Loading notes")
|
console.log("Loading notes")
|
||||||
const notes = {}
|
const notes = {}
|
||||||
@ -194,6 +202,10 @@ export function setupFileLibraryEventHandlers(library, win) {
|
|||||||
return await library.save(path, content)
|
return await library.save(path, content)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('buffer:create', async (event, path, content) => {
|
||||||
|
return await library.create(path, content)
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('buffer:getList', async (event) => {
|
ipcMain.handle('buffer:getList', async (event) => {
|
||||||
return await library.getList()
|
return await library.getList()
|
||||||
});
|
});
|
||||||
|
@ -77,6 +77,10 @@ contextBridge.exposeInMainWorld("heynote", {
|
|||||||
return await ipcRenderer.invoke("buffer:save", path, content)
|
return await ipcRenderer.invoke("buffer:save", path, content)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async create(path, content) {
|
||||||
|
return await ipcRenderer.invoke("buffer:create", path, content)
|
||||||
|
},
|
||||||
|
|
||||||
async saveAndQuit(contents) {
|
async saveAndQuit(contents) {
|
||||||
return await ipcRenderer.invoke("buffer:saveAndQuit", contents)
|
return await ipcRenderer.invoke("buffer:saveAndQuit", contents)
|
||||||
},
|
},
|
||||||
|
52
package-lock.json
generated
52
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"license": "Commons Clause MIT",
|
"license": "Commons Clause MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
"electron-log": "^5.0.1",
|
"electron-log": "^5.0.1",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"semver": "^7.6.3"
|
"semver": "^7.6.3"
|
||||||
@ -1517,6 +1518,57 @@
|
|||||||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@sindresorhus/slugify": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sindresorhus/transliterate": "^1.0.0",
|
||||||
|
"escape-string-regexp": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sindresorhus/slugify/node_modules/escape-string-regexp": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sindresorhus/transliterate": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"escape-string-regexp": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sindresorhus/transliterate/node_modules/escape-string-regexp": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@szmarczak/http-timer": {
|
"node_modules/@szmarczak/http-timer": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
|
||||||
|
@ -78,6 +78,7 @@
|
|||||||
"vue-tsc": "^1.0.16"
|
"vue-tsc": "^1.0.16"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
"electron-log": "^5.0.1",
|
"electron-log": "^5.0.1",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"semver": "^7.6.3"
|
"semver": "^7.6.3"
|
||||||
|
@ -2,9 +2,12 @@
|
|||||||
import { HeynoteEditor } from '../editor/editor.js'
|
import { HeynoteEditor } from '../editor/editor.js'
|
||||||
import { syntaxTree } from "@codemirror/language"
|
import { syntaxTree } from "@codemirror/language"
|
||||||
import { toRaw } from 'vue';
|
import { toRaw } from 'vue';
|
||||||
import { mapState } from 'pinia'
|
import { mapState, mapWritableState, mapActions } from 'pinia'
|
||||||
|
import { useErrorStore } from "../stores/error-store"
|
||||||
import { useNotesStore } from "../stores/notes-store"
|
import { useNotesStore } from "../stores/notes-store"
|
||||||
|
|
||||||
|
const NUM_EDITOR_INSTANCES = 5
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
theme: String,
|
theme: String,
|
||||||
@ -41,8 +44,11 @@
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
syntaxTreeDebugContent: null,
|
syntaxTreeDebugContent: null,
|
||||||
bufferFilePath: null,
|
|
||||||
editor: null,
|
editor: null,
|
||||||
|
editorCache: {
|
||||||
|
lru: [],
|
||||||
|
cache: {}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -130,34 +136,67 @@
|
|||||||
...mapState(useNotesStore, [
|
...mapState(useNotesStore, [
|
||||||
"currentNotePath",
|
"currentNotePath",
|
||||||
]),
|
]),
|
||||||
|
...mapWritableState(useNotesStore, [
|
||||||
|
"currentEditor",
|
||||||
|
"currentNoteName",
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(useErrorStore, ["addError"]),
|
||||||
|
|
||||||
loadBuffer(path) {
|
loadBuffer(path) {
|
||||||
if (this.editor) {
|
if (this.editor) {
|
||||||
this.editor.destroy()
|
this.editor.hide()
|
||||||
}
|
}
|
||||||
// load buffer content and create editor
|
|
||||||
this.bufferFilePath = path
|
if (this.editorCache.cache[path]) {
|
||||||
try {
|
// editor is already loaded, just switch to it
|
||||||
this.editor = new HeynoteEditor({
|
console.log("Switching to cached editor", path)
|
||||||
element: this.$refs.editor,
|
toRaw(this.editor).hide()
|
||||||
path: this.bufferFilePath,
|
this.editor = this.editorCache.cache[path]
|
||||||
theme: this.theme,
|
toRaw(this.editor).show()
|
||||||
keymap: this.keymap,
|
//toRaw(this.editor).currenciesLoaded()
|
||||||
emacsMetaKey: this.emacsMetaKey,
|
this.currentEditor = toRaw(this.editor)
|
||||||
showLineNumberGutter: this.showLineNumberGutter,
|
|
||||||
showFoldGutter: this.showFoldGutter,
|
|
||||||
bracketClosing: this.bracketClosing,
|
|
||||||
fontFamily: this.fontFamily,
|
|
||||||
fontSize: this.fontSize,
|
|
||||||
defaultBlockToken: this.defaultBlockLanguage,
|
|
||||||
defaultBlockAutoDetect: this.defaultBlockLanguageAutoDetect,
|
|
||||||
})
|
|
||||||
window._heynote_editor = toRaw(this.editor)
|
window._heynote_editor = toRaw(this.editor)
|
||||||
} catch (e) {
|
// move to end of LRU
|
||||||
alert("Error! " + e.message)
|
this.editorCache.lru = this.editorCache.lru.filter(p => p !== path)
|
||||||
throw e
|
this.editorCache.lru.push(path)
|
||||||
|
} else {
|
||||||
|
// check if we need to free up a slot
|
||||||
|
if (this.editorCache.lru.length >= NUM_EDITOR_INSTANCES) {
|
||||||
|
const pathToFree = this.editorCache.lru.shift()
|
||||||
|
console.log("Freeing up editor slot", pathToFree)
|
||||||
|
this.editorCache.cache[pathToFree].destroy()
|
||||||
|
delete this.editorCache.cache[pathToFree]
|
||||||
|
this.editorCache.lru = this.editorCache.lru.filter(p => p !== pathToFree)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new Editor instance
|
||||||
|
console.log("Loading new editor", path)
|
||||||
|
try {
|
||||||
|
this.editor = new HeynoteEditor({
|
||||||
|
element: this.$refs.editor,
|
||||||
|
path: path,
|
||||||
|
theme: this.theme,
|
||||||
|
keymap: this.keymap,
|
||||||
|
emacsMetaKey: this.emacsMetaKey,
|
||||||
|
showLineNumberGutter: this.showLineNumberGutter,
|
||||||
|
showFoldGutter: this.showFoldGutter,
|
||||||
|
bracketClosing: this.bracketClosing,
|
||||||
|
fontFamily: this.fontFamily,
|
||||||
|
fontSize: this.fontSize,
|
||||||
|
defaultBlockToken: this.defaultBlockLanguage,
|
||||||
|
defaultBlockAutoDetect: this.defaultBlockLanguageAutoDetect,
|
||||||
|
})
|
||||||
|
this.currentEditor = toRaw(this.editor)
|
||||||
|
window._heynote_editor = toRaw(this.editor)
|
||||||
|
this.editorCache.cache[path] = this.editor
|
||||||
|
this.editorCache.lru.push(path)
|
||||||
|
} catch (e) {
|
||||||
|
this.addError("Error! " + e.message)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import slugify from '@sindresorhus/slugify';
|
||||||
|
|
||||||
import { mapState, mapActions } from 'pinia'
|
import { mapState, mapActions } from 'pinia'
|
||||||
import { useNotesStore } from "../stores/notes-store"
|
import { useNotesStore } from "../stores/notes-store"
|
||||||
|
|
||||||
@ -12,6 +14,9 @@
|
|||||||
tags: [],
|
tags: [],
|
||||||
directoryTree: null,
|
directoryTree: null,
|
||||||
parentPath: "",
|
parentPath: "",
|
||||||
|
errors: {
|
||||||
|
name: null,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
@ -64,17 +69,27 @@
|
|||||||
currentNoteDirectory() {
|
currentNoteDirectory() {
|
||||||
return this.currentNotePath.split("/").slice(0, -1).join("/")
|
return this.currentNotePath.split("/").slice(0, -1).join("/")
|
||||||
},
|
},
|
||||||
|
|
||||||
|
nameInputClass() {
|
||||||
|
return {
|
||||||
|
"name-input": true,
|
||||||
|
"error": this.errors.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useNotesStore, [
|
...mapActions(useNotesStore, [
|
||||||
"updateNotes",
|
"updateNotes",
|
||||||
|
"createNewNoteFromActiveBlock",
|
||||||
]),
|
]),
|
||||||
|
|
||||||
onKeydown(event) {
|
onKeydown(event) {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
this.$emit("close")
|
this.$emit("close")
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
} if (event.key === "Enter") {
|
||||||
|
this.submit()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -87,9 +102,29 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onSubmit(event) {
|
submit() {
|
||||||
event.preventDefault()
|
let slug = slugify(this.name)
|
||||||
console.log("Creating note", this.name)
|
if (slug === "") {
|
||||||
|
this.errors.name = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const parentPathPrefix = this.parentPath === "" ? "" : this.parentPath + "/"
|
||||||
|
let path;
|
||||||
|
for (let i=0; i<1000; i++) {
|
||||||
|
let filename = slug + ".txt"
|
||||||
|
path = parentPathPrefix + filename
|
||||||
|
if (!this.notes[path]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
slug = slugify(this.name + "-" + i)
|
||||||
|
}
|
||||||
|
if (this.notes[path]) {
|
||||||
|
console.error("Failed to create note, path already exists", path)
|
||||||
|
this.errors.name = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log("Creating note", path)
|
||||||
|
this.createNewNoteFromActiveBlock(path, this.name)
|
||||||
this.$emit("close")
|
this.$emit("close")
|
||||||
//this.$emit("create", this.$refs.input.value)
|
//this.$emit("create", this.$refs.input.value)
|
||||||
},
|
},
|
||||||
@ -99,16 +134,17 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="fader" @keydown="onKeydown" tabindex="-1">
|
<div class="fader" @keydown="onKeydown" tabindex="-1">
|
||||||
<form class="new-note" tabindex="-1" @focusout="onFocusOut" ref="container" @submit="onSubmit">
|
<form class="new-note" tabindex="-1" @focusout="onFocusOut" ref="container" @submit.prevent="submit">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>New Note from Block</h1>
|
<h1>New Note from Block</h1>
|
||||||
<input
|
<input
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="name"
|
v-model="name"
|
||||||
class="name-input"
|
:class="nameInputClass"
|
||||||
ref="nameInput"
|
ref="nameInput"
|
||||||
@keydown="onInputKeydown"
|
@keydown="onInputKeydown"
|
||||||
|
@input="errors.name = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label for="folder-select">Create in</label>
|
<label for="folder-select">Create in</label>
|
||||||
@ -189,6 +225,8 @@
|
|||||||
outline: none
|
outline: none
|
||||||
border: 1px solid #fff
|
border: 1px solid #fff
|
||||||
outline: 2px solid #48b57e
|
outline: 2px solid #48b57e
|
||||||
|
&.error
|
||||||
|
background: #ffe9e9
|
||||||
+dark-mode
|
+dark-mode
|
||||||
background: #3b3b3b
|
background: #3b3b3b
|
||||||
color: rgba(255,255,255, 0.9)
|
color: rgba(255,255,255, 0.9)
|
||||||
@ -219,4 +257,3 @@
|
|||||||
outline-color: #48b57e
|
outline-color: #48b57e
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
./folder-selector/FolderSelector.vue
|
|
@ -19,6 +19,8 @@
|
|||||||
return {
|
return {
|
||||||
"path": path,
|
"path": path,
|
||||||
"name": metadata?.name || path,
|
"name": metadata?.name || path,
|
||||||
|
"folder": path.split("/").slice(0, -1).join("/"),
|
||||||
|
"scratch": path === "buffer-dev.txt",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -82,6 +84,13 @@
|
|||||||
this.$emit("close")
|
this.$emit("close")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getItemClass(item, idx) {
|
||||||
|
return {
|
||||||
|
"selected": idx === this.selected,
|
||||||
|
"scratch": item.scratch,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -100,12 +109,12 @@
|
|||||||
<li
|
<li
|
||||||
v-for="item, idx in filteredItems"
|
v-for="item, idx in filteredItems"
|
||||||
:key="item.path"
|
:key="item.path"
|
||||||
:class="idx === selected ? 'selected' : ''"
|
:class="getItemClass(item, idx)"
|
||||||
@click="selectItem(item.path)"
|
@click="selectItem(item.path)"
|
||||||
ref="item"
|
ref="item"
|
||||||
>
|
>
|
||||||
<span class="name">{{ item.name }}</span>
|
<span class="name">{{ item.name }}</span>
|
||||||
<span class="path">{{ item.path }}</span>
|
<span class="path">{{ item.folder }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</form>
|
</form>
|
||||||
@ -128,6 +137,7 @@
|
|||||||
position: absolute
|
position: absolute
|
||||||
top: 0
|
top: 0
|
||||||
left: 50%
|
left: 50%
|
||||||
|
width: 420px
|
||||||
transform: translateX(-50%)
|
transform: translateX(-50%)
|
||||||
max-height: 100%
|
max-height: 100%
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
@ -176,6 +186,8 @@
|
|||||||
&.selected
|
&.selected
|
||||||
background: #48b57e
|
background: #48b57e
|
||||||
color: #fff
|
color: #fff
|
||||||
|
&.scratch
|
||||||
|
font-weight: 600
|
||||||
+dark-mode
|
+dark-mode
|
||||||
color: rgba(255,255,255, 0.53)
|
color: rgba(255,255,255, 0.53)
|
||||||
&:hover
|
&:hover
|
||||||
@ -185,7 +197,15 @@
|
|||||||
color: rgba(255,255,255, 0.87)
|
color: rgba(255,255,255, 0.87)
|
||||||
.name
|
.name
|
||||||
margin-right: 12px
|
margin-right: 12px
|
||||||
|
flex-shrink: 0
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
text-wrap: nowrap
|
||||||
.path
|
.path
|
||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
font-size: 12px
|
font-size: 12px
|
||||||
|
flex-shrink: 1
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
text-wrap: nowrap
|
||||||
</style>
|
</style>
|
||||||
|
@ -5,3 +5,5 @@ export const LANGUAGE_CHANGE = "heynote-change"
|
|||||||
export const CURRENCIES_LOADED = "heynote-currencies-loaded"
|
export const CURRENCIES_LOADED = "heynote-currencies-loaded"
|
||||||
export const SET_CONTENT = "heynote-set-content"
|
export const SET_CONTENT = "heynote-set-content"
|
||||||
export const ADD_NEW_BLOCK = "heynote-add-new-block"
|
export const ADD_NEW_BLOCK = "heynote-add-new-block"
|
||||||
|
export const DELETE_BLOCK = "heynote-delete-block"
|
||||||
|
export const CURSOR_CHANGE = "heynote-cursor-change"
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { ViewPlugin, EditorView, Decoration, WidgetType, lineNumbers } from "@codemirror/view"
|
import { ViewPlugin, EditorView, Decoration, WidgetType, lineNumbers } from "@codemirror/view"
|
||||||
import { layer, RectangleMarker } from "@codemirror/view"
|
import { layer, RectangleMarker } from "@codemirror/view"
|
||||||
import { EditorState, RangeSetBuilder, StateField, Facet , StateEffect, RangeSet} from "@codemirror/state";
|
import { EditorState, RangeSetBuilder, StateField, Facet , StateEffect, RangeSet, Transaction} from "@codemirror/state";
|
||||||
import { syntaxTree, ensureSyntaxTree, syntaxTreeAvailable } from "@codemirror/language"
|
import { syntaxTree, ensureSyntaxTree, syntaxTreeAvailable } from "@codemirror/language"
|
||||||
import { Note, Document, NoteDelimiter } from "../lang-heynote/parser.terms.js"
|
import { Note, Document, NoteDelimiter } from "../lang-heynote/parser.terms.js"
|
||||||
import { IterMode } from "@lezer/common";
|
import { IterMode } from "@lezer/common";
|
||||||
import { useNotesStore } from "../../stores/notes-store.js"
|
import { useNotesStore } from "../../stores/notes-store.js"
|
||||||
import { heynoteEvent, LANGUAGE_CHANGE } from "../annotation.js";
|
import { heynoteEvent, LANGUAGE_CHANGE, CURSOR_CHANGE } from "../annotation.js";
|
||||||
import { mathBlock } from "./math.js"
|
import { mathBlock } from "./math.js"
|
||||||
import { emptyBlockSelected } from "./select-all.js";
|
import { emptyBlockSelected } from "./select-all.js";
|
||||||
|
|
||||||
@ -404,6 +404,15 @@ function getSelectionSize(state, sel) {
|
|||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function triggerCursorChange({state, dispatch}) {
|
||||||
|
// Trigger empty change transaction that is annotated with CURRENCIES_LOADED
|
||||||
|
// This will make Math blocks re-render so that currency conversions are applied
|
||||||
|
dispatch(state.update({
|
||||||
|
changes:{from: 0, to: 0, insert:""},
|
||||||
|
annotations: [heynoteEvent.of(CURSOR_CHANGE), Transaction.addToHistory.of(false)],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
const emitCursorChange = (editor) => {
|
const emitCursorChange = (editor) => {
|
||||||
const notesStore = useNotesStore()
|
const notesStore = useNotesStore()
|
||||||
return ViewPlugin.fromClass(
|
return ViewPlugin.fromClass(
|
||||||
@ -411,8 +420,8 @@ const emitCursorChange = (editor) => {
|
|||||||
update(update) {
|
update(update) {
|
||||||
// if the selection changed or the language changed (can happen without selection change),
|
// if the selection changed or the language changed (can happen without selection change),
|
||||||
// emit a selection change event
|
// emit a selection change event
|
||||||
const langChange = update.transactions.some(tr => tr.annotations.some(a => a.value == LANGUAGE_CHANGE))
|
const shouldUpdate = update.transactions.some(tr => tr.annotations.some(a => a.value == LANGUAGE_CHANGE || a.value == CURSOR_CHANGE))
|
||||||
if (update.selectionSet || langChange) {
|
if (update.selectionSet || shouldUpdate) {
|
||||||
const cursorLine = getBlockLineFromPos(update.state, update.state.selection.main.head)
|
const cursorLine = getBlockLineFromPos(update.state, update.state.selection.main.head)
|
||||||
|
|
||||||
const selectionSize = update.state.selection.ranges.map(
|
const selectionSize = update.state.selection.ranges.map(
|
||||||
@ -425,6 +434,7 @@ const emitCursorChange = (editor) => {
|
|||||||
notesStore.currentSelectionSize = selectionSize
|
notesStore.currentSelectionSize = selectionSize
|
||||||
notesStore.currentLanguage = block.language.name
|
notesStore.currentLanguage = block.language.name
|
||||||
notesStore.currentLanguageAuto = block.language.auto
|
notesStore.currentLanguageAuto = block.language.auto
|
||||||
|
notesStore.currentNoteName = editor.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { EditorSelection } from "@codemirror/state"
|
import { EditorSelection, Transaction } from "@codemirror/state"
|
||||||
import { heynoteEvent, LANGUAGE_CHANGE, CURRENCIES_LOADED, ADD_NEW_BLOCK } from "../annotation.js";
|
import { heynoteEvent, LANGUAGE_CHANGE, CURRENCIES_LOADED, ADD_NEW_BLOCK, DELETE_BLOCK } from "../annotation.js";
|
||||||
import { blockState, getActiveNoteBlock, getFirstNoteBlock, getLastNoteBlock, getNoteBlockFromPos } from "./block"
|
import { blockState, getActiveNoteBlock, getFirstNoteBlock, getLastNoteBlock, getNoteBlockFromPos } from "./block"
|
||||||
import { moveLineDown, moveLineUp } from "./move-lines.js";
|
import { moveLineDown, moveLineUp } from "./move-lines.js";
|
||||||
import { selectAll } from "./select-all.js";
|
import { selectAll } from "./select-all.js";
|
||||||
@ -7,7 +7,7 @@ import { selectAll } from "./select-all.js";
|
|||||||
export { moveLineDown, moveLineUp, selectAll }
|
export { moveLineDown, moveLineUp, selectAll }
|
||||||
|
|
||||||
|
|
||||||
function getBlockDelimiter(defaultToken, autoDetect) {
|
export function getBlockDelimiter(defaultToken, autoDetect) {
|
||||||
return `\n∞∞∞${autoDetect ? defaultToken + '-a' : defaultToken}\n`
|
return `\n∞∞∞${autoDetect ? defaultToken + '-a' : defaultToken}\n`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,6 +317,24 @@ export function triggerCurrenciesLoaded(state, dispatch) {
|
|||||||
// This will make Math blocks re-render so that currency conversions are applied
|
// This will make Math blocks re-render so that currency conversions are applied
|
||||||
dispatch(state.update({
|
dispatch(state.update({
|
||||||
changes:{from: 0, to: 0, insert:""},
|
changes:{from: 0, to: 0, insert:""},
|
||||||
annotations: [heynoteEvent.of(CURRENCIES_LOADED)],
|
annotations: [heynoteEvent.of(CURRENCIES_LOADED), Transaction.addToHistory.of(false)],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteBlock = (editor) => ({state, dispatch}) => {
|
||||||
|
const block = getActiveNoteBlock(state)
|
||||||
|
const blocks = state.facet(blockState)
|
||||||
|
let replace = ""
|
||||||
|
if (blocks.length == 1) {
|
||||||
|
replace = getBlockDelimiter(editor.defaultBlockToken, editor.defaultBlockAutoDetect)
|
||||||
|
}
|
||||||
|
dispatch(state.update({
|
||||||
|
changes: {
|
||||||
|
from: block.range.from,
|
||||||
|
to: block.range.to,
|
||||||
|
insert: replace,
|
||||||
|
},
|
||||||
|
selection: EditorSelection.cursor(block.delimiter.from),
|
||||||
|
annotations: [heynoteEvent.of(DELETE_BLOCK)],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ import { heynoteBase } from "./theme/base.js"
|
|||||||
import { getFontTheme } from "./theme/font-theme.js";
|
import { getFontTheme } from "./theme/font-theme.js";
|
||||||
import { customSetup } from "./setup.js"
|
import { customSetup } from "./setup.js"
|
||||||
import { heynoteLang } from "./lang-heynote/heynote.js"
|
import { heynoteLang } from "./lang-heynote/heynote.js"
|
||||||
import { noteBlockExtension, blockLineNumbers, blockState } from "./block/block.js"
|
import { noteBlockExtension, blockLineNumbers, blockState, getActiveNoteBlock, triggerCursorChange } from "./block/block.js"
|
||||||
import { heynoteEvent, SET_CONTENT } from "./annotation.js";
|
import { heynoteEvent, SET_CONTENT, DELETE_BLOCK } from "./annotation.js";
|
||||||
import { changeCurrentBlockLanguage, triggerCurrenciesLoaded } from "./block/commands.js"
|
import { changeCurrentBlockLanguage, triggerCurrenciesLoaded, getBlockDelimiter, deleteBlock } from "./block/commands.js"
|
||||||
import { formatBlockContent } from "./block/format-code.js"
|
import { formatBlockContent } from "./block/format-code.js"
|
||||||
import { heynoteKeymap } from "./keymap.js"
|
import { heynoteKeymap } from "./keymap.js"
|
||||||
import { emacsKeymap } from "./emacs.js"
|
import { emacsKeymap } from "./emacs.js"
|
||||||
@ -66,6 +66,7 @@ export class HeynoteEditor {
|
|||||||
this.setDefaultBlockLanguage(defaultBlockToken, defaultBlockAutoDetect)
|
this.setDefaultBlockLanguage(defaultBlockToken, defaultBlockAutoDetect)
|
||||||
this.contentLoaded = false
|
this.contentLoaded = false
|
||||||
this.notesStore = useNotesStore()
|
this.notesStore = useNotesStore()
|
||||||
|
this.name = ""
|
||||||
|
|
||||||
|
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
@ -179,7 +180,8 @@ export class HeynoteEditor {
|
|||||||
this.setReadOnly(true)
|
this.setReadOnly(true)
|
||||||
throw new Error(`Failed to load note: ${e.message}`)
|
throw new Error(`Failed to load note: ${e.message}`)
|
||||||
}
|
}
|
||||||
this.notesStore.currentNoteName = this.note.metadata?.name || this.path
|
this.name = this.note.metadata?.name || this.path
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// set buffer content
|
// set buffer content
|
||||||
this.view.dispatch({
|
this.view.dispatch({
|
||||||
@ -262,7 +264,24 @@ export class HeynoteEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openCreateNote() {
|
openCreateNote() {
|
||||||
this.notesStore.openCreateNote()
|
this.notesStore.openCreateNote(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNewNoteFromActiveBlock(path, name) {
|
||||||
|
const block = getActiveNoteBlock(this.view.state)
|
||||||
|
if (!block) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const data = this.view.state.sliceDoc(block.range.from, block.range.to)
|
||||||
|
await this.notesStore.saveNewNote(path, name, data)
|
||||||
|
deleteBlock(this)(this.view)
|
||||||
|
|
||||||
|
// by using requestAnimationFrame we avoid a race condition where rendering the block backgrounds
|
||||||
|
// would fail if we immediately opened the new note (since the block UI wouldn't have time to update
|
||||||
|
// after the block was deleted)
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.notesStore.openNote(path)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentLanguage(lang, auto=false) {
|
setCurrentLanguage(lang, auto=false) {
|
||||||
@ -311,6 +330,16 @@ export class HeynoteEditor {
|
|||||||
this.view.destroy()
|
this.view.destroy()
|
||||||
window.heynote.buffer.close(this.path)
|
window.heynote.buffer.close(this.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
console.log("hiding element", this.view.dom)
|
||||||
|
this.view.dom.style.setProperty("display", "none", "important")
|
||||||
|
}
|
||||||
|
show() {
|
||||||
|
console.log("showing element", this.view.dom)
|
||||||
|
this.view.dom.style.setProperty("display", "")
|
||||||
|
triggerCursorChange(this.view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
gotoPreviousParagraph, gotoNextParagraph,
|
gotoPreviousParagraph, gotoNextParagraph,
|
||||||
selectNextParagraph, selectPreviousParagraph,
|
selectNextParagraph, selectPreviousParagraph,
|
||||||
newCursorBelow, newCursorAbove,
|
newCursorBelow, newCursorAbove,
|
||||||
|
deleteBlock,
|
||||||
} from "./block/commands.js"
|
} from "./block/commands.js"
|
||||||
import { pasteCommand, copyCommand, cutCommand } from "./copy-paste.js"
|
import { pasteCommand, copyCommand, cutCommand } from "./copy-paste.js"
|
||||||
|
|
||||||
@ -59,6 +60,7 @@ export function heynoteKeymap(editor) {
|
|||||||
["Mod-l", () => editor.openLanguageSelector()],
|
["Mod-l", () => editor.openLanguageSelector()],
|
||||||
["Mod-p", () => editor.openNoteSelector()],
|
["Mod-p", () => editor.openNoteSelector()],
|
||||||
["Mod-s", () => editor.openCreateNote()],
|
["Mod-s", () => editor.openCreateNote()],
|
||||||
|
["Mod-Shift-d", deleteBlock(editor)],
|
||||||
["Alt-Shift-f", formatBlockContent],
|
["Alt-Shift-f", formatBlockContent],
|
||||||
["Mod-Alt-ArrowDown", newCursorBelow],
|
["Mod-Alt-ArrowDown", newCursorBelow],
|
||||||
["Mod-Alt-ArrowUp", newCursorAbove],
|
["Mod-Alt-ArrowUp", newCursorAbove],
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
import { toRaw } from 'vue';
|
||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
|
import { NoteFormat } from "../editor/note-format"
|
||||||
|
|
||||||
export const useNotesStore = defineStore("notes", {
|
export const useNotesStore = defineStore("notes", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
notes: {},
|
notes: {},
|
||||||
|
currentEditor: null,
|
||||||
currentNotePath: window.heynote.isDev ? "buffer-dev.txt" : "buffer.txt",
|
currentNotePath: window.heynote.isDev ? "buffer-dev.txt" : "buffer.txt",
|
||||||
currentNoteName: null,
|
currentNoteName: null,
|
||||||
currentLanguage: null,
|
currentLanguage: null,
|
||||||
@ -24,11 +27,6 @@ export const useNotesStore = defineStore("notes", {
|
|||||||
this.notes = notes
|
this.notes = notes
|
||||||
},
|
},
|
||||||
|
|
||||||
createNewNote(path, content) {
|
|
||||||
//window.heynote.buffer.save(path, content)
|
|
||||||
this.updateNotes()
|
|
||||||
},
|
|
||||||
|
|
||||||
openNote(path) {
|
openNote(path) {
|
||||||
this.showNoteSelector = false
|
this.showNoteSelector = false
|
||||||
this.showLanguageSelector = false
|
this.showLanguageSelector = false
|
||||||
@ -56,6 +54,26 @@ export const useNotesStore = defineStore("notes", {
|
|||||||
this.showNoteSelector = false
|
this.showNoteSelector = false
|
||||||
this.showLanguageSelector = false
|
this.showLanguageSelector = false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async createNewNoteFromActiveBlock(path, name) {
|
||||||
|
await toRaw(this.currentEditor).createNewNoteFromActiveBlock(path, name)
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveNewNote(path, name, content) {
|
||||||
|
//window.heynote.buffer.save(path, content)
|
||||||
|
//this.updateNotes()
|
||||||
|
|
||||||
|
if (this.notes[path]) {
|
||||||
|
throw new Error(`Note already exists: ${path}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const note = new NoteFormat()
|
||||||
|
note.content = content
|
||||||
|
note.metadata.name = name
|
||||||
|
console.log("saving", path, note.serialize())
|
||||||
|
await window.heynote.buffer.create(path, note.serialize())
|
||||||
|
this.updateNotes()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Exception } from "sass";
|
||||||
import { SETTINGS_CHANGE_EVENT, OPEN_SETTINGS_EVENT } from "../electron/constants";
|
import { SETTINGS_CHANGE_EVENT, OPEN_SETTINGS_EVENT } from "../electron/constants";
|
||||||
|
|
||||||
const mediaMatch = window.matchMedia('(prefers-color-scheme: dark)')
|
const mediaMatch = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
@ -90,11 +91,14 @@ const Heynote = {
|
|||||||
localStorage.setItem(path, content)
|
localStorage.setItem(path, content)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async create(path, content) {
|
||||||
|
throw Exception("Not implemented")
|
||||||
|
},
|
||||||
|
|
||||||
async saveAndQuit(contents) {
|
async saveAndQuit(contents) {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
async exists(path) {
|
async exists(path) {
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user