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:
Jonatan Heyman 2024-07-26 11:30:25 +02:00
parent dbf675a28a
commit 4b39078689
14 changed files with 298 additions and 50 deletions

View File

@ -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()
}); });

View File

@ -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
View File

@ -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",

View File

@ -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"

View File

@ -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
}
} }
}, },

View File

@ -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

View File

@ -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>

View File

@ -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"

View File

@ -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
} }
} }
} }

View File

@ -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)],
})) }))
} }

View File

@ -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)
}
} }

View File

@ -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],

View File

@ -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()
},
}, },
}) })

View File

@ -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
}, },