mirror of
https://github.com/heyman/heynote.git
synced 2024-12-22 23:00:38 +01:00
Implement support for editing notes' metadata, and ability to move notes into other directories.
Create separate pinia store for the editor cache functionality.
This commit is contained in:
parent
0da3e32171
commit
7e1f01471a
1
assets/icons/arrow-right.svg
Normal file
1
assets/icons/arrow-right.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg baseProfile="tiny" height="24px" version="1.2" viewBox="0 0 24 24" width="24px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1"><path fill="#ddd" d="M13.293,7.293c-0.391,0.391-0.391,1.023,0,1.414L15.586,11H8c-0.552,0-1,0.448-1,1s0.448,1,1,1h7.586l-2.293,2.293 c-0.391,0.391-0.391,1.023,0,1.414C13.488,16.902,13.744,17,14,17s0.512-0.098,0.707-0.293L19.414,12l-4.707-4.707 C14.316,6.902,13.684,6.902,13.293,7.293z"/></g></svg>
|
After Width: | Height: | Size: 522 B |
@ -72,6 +72,15 @@ export class FileLibrary {
|
||||
await this.jetpack.writeAsync(fullPath, content)
|
||||
}
|
||||
|
||||
async move(path, newPath) {
|
||||
if (await this.exists(newPath)) {
|
||||
throw new Error(`File already exists: ${newPath}`)
|
||||
}
|
||||
const fullOldPath = join(this.basePath, path)
|
||||
const fullNewPath = join(this.basePath, newPath)
|
||||
await this.jetpack.moveAsync(fullOldPath, fullNewPath)
|
||||
}
|
||||
|
||||
async getList() {
|
||||
console.log("Loading notes")
|
||||
const notes = {}
|
||||
@ -231,5 +240,9 @@ export function setupFileLibraryEventHandlers(library, win) {
|
||||
app.quit()
|
||||
})
|
||||
|
||||
ipcMain.handle('buffer:move', async (event, path, newPath) => {
|
||||
return await library.move(path, newPath)
|
||||
});
|
||||
|
||||
library.setupWatcher(win)
|
||||
}
|
||||
|
@ -77,6 +77,10 @@ contextBridge.exposeInMainWorld("heynote", {
|
||||
return await ipcRenderer.invoke("buffer:save", path, content)
|
||||
},
|
||||
|
||||
async move(path, newPath) {
|
||||
return await ipcRenderer.invoke("buffer:move", path, newPath)
|
||||
},
|
||||
|
||||
async create(path, content) {
|
||||
return await ipcRenderer.invoke("buffer:create", path, content)
|
||||
},
|
||||
|
@ -12,6 +12,7 @@
|
||||
import Settings from './settings/Settings.vue'
|
||||
import ErrorMessages from './ErrorMessages.vue'
|
||||
import NewNote from './NewNote.vue'
|
||||
import EditNote from './EditNote.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -22,6 +23,7 @@
|
||||
NoteSelector,
|
||||
ErrorMessages,
|
||||
NewNote,
|
||||
EditNote,
|
||||
},
|
||||
|
||||
data() {
|
||||
@ -67,6 +69,7 @@
|
||||
showLanguageSelector(value) { this.dialogWatcher(value) },
|
||||
showNoteSelector(value) { this.dialogWatcher(value) },
|
||||
showCreateNote(value) { this.dialogWatcher(value) },
|
||||
showEditNote(value) { this.dialogWatcher(value) },
|
||||
|
||||
currentNotePath() {
|
||||
this.focusEditor()
|
||||
@ -79,10 +82,11 @@
|
||||
"showLanguageSelector",
|
||||
"showNoteSelector",
|
||||
"showCreateNote",
|
||||
"showEditNote",
|
||||
]),
|
||||
|
||||
editorInert() {
|
||||
return this.showCreateNote || this.showSettings
|
||||
return this.showCreateNote || this.showSettings || this.showEditNote
|
||||
},
|
||||
},
|
||||
|
||||
@ -92,6 +96,7 @@
|
||||
"openNoteSelector",
|
||||
"openCreateNote",
|
||||
"closeDialog",
|
||||
"closeNoteSelector",
|
||||
"openNote",
|
||||
]),
|
||||
|
||||
@ -186,7 +191,7 @@
|
||||
<NoteSelector
|
||||
v-if="showNoteSelector"
|
||||
@openNote="openNote"
|
||||
@close="closeDialog"
|
||||
@close="closeNoteSelector"
|
||||
/>
|
||||
<Settings
|
||||
v-if="showSettings"
|
||||
@ -197,6 +202,10 @@
|
||||
v-if="showCreateNote"
|
||||
@close="closeDialog"
|
||||
/>
|
||||
<EditNote
|
||||
v-if="showEditNote"
|
||||
@close="closeDialog"
|
||||
/>
|
||||
<ErrorMessages />
|
||||
</div>
|
||||
</div>
|
||||
|
274
src/components/EditNote.vue
Normal file
274
src/components/EditNote.vue
Normal file
@ -0,0 +1,274 @@
|
||||
<script>
|
||||
import slugify from '@sindresorhus/slugify';
|
||||
|
||||
import { toRaw } from 'vue';
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import { useNotesStore } from "../stores/notes-store"
|
||||
|
||||
import FolderSelector from './folder-selector/FolderSelector.vue'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
name: "",
|
||||
filename: "",
|
||||
tags: [],
|
||||
directoryTree: null,
|
||||
parentPath: "",
|
||||
errors: {
|
||||
name: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
components: {
|
||||
FolderSelector
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.$refs.nameInput.focus()
|
||||
this.updateNotes()
|
||||
|
||||
console.log("EditNote mounted", this.currentNote)
|
||||
this.name = this.currentNote.name
|
||||
|
||||
// build directory tree
|
||||
const directories = await window.heynote.buffer.getDirectoryList()
|
||||
const rootNode = {
|
||||
name: "Heynote Root",
|
||||
path: "",
|
||||
children: [],
|
||||
open: true,
|
||||
}
|
||||
const getNodeFromList = (list, part) => list.find(node => node.name === part)
|
||||
|
||||
directories.forEach((path) => {
|
||||
const parts = path.split("/")
|
||||
let currentLevel = rootNode
|
||||
let currentParts = []
|
||||
parts.forEach(part => {
|
||||
currentParts.push(part)
|
||||
let node = getNodeFromList(currentLevel.children, part)
|
||||
if (node) {
|
||||
currentLevel = node
|
||||
} else {
|
||||
const currentPath = currentParts.join("/")
|
||||
node = {
|
||||
name: part,
|
||||
children: [],
|
||||
path: currentPath,
|
||||
open: this.currentNotePath.startsWith(currentPath),
|
||||
}
|
||||
currentLevel.children.push(node)
|
||||
currentLevel = node
|
||||
}
|
||||
})
|
||||
})
|
||||
//console.log("tree:", rootNode)
|
||||
this.directoryTree = rootNode
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(useNotesStore, [
|
||||
"notes",
|
||||
"currentNotePath",
|
||||
]),
|
||||
|
||||
currentNote() {
|
||||
return this.notes[this.currentNotePath]
|
||||
},
|
||||
|
||||
currentNoteDirectory() {
|
||||
return this.currentNotePath.split("/").slice(0, -1).join("/")
|
||||
},
|
||||
|
||||
nameInputClass() {
|
||||
return {
|
||||
"name-input": true,
|
||||
"error": this.errors.name,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions(useNotesStore, [
|
||||
"updateNotes",
|
||||
"updateNoteMetadata",
|
||||
]),
|
||||
|
||||
onKeydown(event) {
|
||||
if (event.key === "Escape") {
|
||||
this.$emit("close")
|
||||
event.preventDefault()
|
||||
} if (event.key === "Enter") {
|
||||
this.submit()
|
||||
event.preventDefault()
|
||||
}
|
||||
},
|
||||
|
||||
onInputKeydown(event) {
|
||||
// redirect arrow keys and page up/down to folder selector
|
||||
const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"]
|
||||
if (redirectKeys.includes(event.key)) {
|
||||
this.$refs.folderSelect.$el.dispatchEvent(new KeyboardEvent("keydown", {key: event.key}))
|
||||
event.preventDefault()
|
||||
}
|
||||
},
|
||||
|
||||
submit() {
|
||||
let slug = slugify(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 (path === this.currentNotePath || !this.notes[path]) {
|
||||
// file name is ok if it's the current note, or if it doesn't exist
|
||||
break
|
||||
}
|
||||
slug = slugify(this.name + "-" + i)
|
||||
}
|
||||
if (path !== this.currentNotePath && this.notes[path]) {
|
||||
console.error("Failed to edit note, path already exists", path)
|
||||
this.errors.name = true
|
||||
return
|
||||
}
|
||||
console.log("Update note", path)
|
||||
this.updateNoteMetadata(this.currentNotePath, this.name, path)
|
||||
this.$emit("close")
|
||||
//this.$emit("create", this.$refs.input.value)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fader" @keydown.stop="onKeydown" tabindex="-1">
|
||||
<form class="new-note" tabindex="-1" @focusout="onFocusOut" ref="container" @submit.prevent="submit">
|
||||
<div class="container">
|
||||
<h1>Edit Note</h1>
|
||||
<input
|
||||
placeholder="Name"
|
||||
type="text"
|
||||
v-model="name"
|
||||
:class="nameInputClass"
|
||||
ref="nameInput"
|
||||
@keydown="onInputKeydown"
|
||||
@input="errors.name = false"
|
||||
/>
|
||||
|
||||
<label for="folder-select">Move to</label>
|
||||
<FolderSelector
|
||||
v-if="directoryTree"
|
||||
:directoryTree="directoryTree"
|
||||
:selectedPath="currentNoteDirectory"
|
||||
id="folder-select"
|
||||
v-model="parentPath"
|
||||
ref="folderSelect"
|
||||
/>
|
||||
</div>
|
||||
<div class="bottom-bar">
|
||||
<button type="submit">Create Note</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="sass">
|
||||
.fader
|
||||
position: fixed
|
||||
top: 0
|
||||
left: 0
|
||||
bottom: 0
|
||||
right: 0
|
||||
background: rgba(0,0,0, 0.2)
|
||||
.new-note
|
||||
font-size: 13px
|
||||
//background: #48b57e
|
||||
background: #efefef
|
||||
width: 420px
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 50%
|
||||
transform: translateX(-50%)
|
||||
border-radius: 0 0 5px 5px
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.3)
|
||||
display: flex
|
||||
flex-direction: column
|
||||
max-height: 100%
|
||||
&:focus
|
||||
outline: none
|
||||
+dark-mode
|
||||
background: #151516
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.5)
|
||||
color: rgba(255,255,255, 0.7)
|
||||
+webapp-mobile
|
||||
max-width: calc(100% - 80px)
|
||||
|
||||
.container
|
||||
padding: 10px
|
||||
min-height: 0
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
h1
|
||||
font-weight: bold
|
||||
margin-bottom: 14px
|
||||
|
||||
label
|
||||
display: block
|
||||
margin-bottom: 6px
|
||||
//padding-left: 2px
|
||||
font-size: 12px
|
||||
font-weight: 600
|
||||
|
||||
|
||||
.name-input
|
||||
width: 100%
|
||||
background: #fff
|
||||
padding: 4px 5px
|
||||
border: 1px solid #ccc
|
||||
box-sizing: border-box
|
||||
border-radius: 2px
|
||||
margin-bottom: 16px
|
||||
&:focus
|
||||
outline: none
|
||||
border: 1px solid #fff
|
||||
outline: 2px solid #48b57e
|
||||
&.error
|
||||
background: #ffe9e9
|
||||
+dark-mode
|
||||
background: #3b3b3b
|
||||
color: rgba(255,255,255, 0.9)
|
||||
border: 1px solid #5a5a5a
|
||||
&:focus
|
||||
border: 1px solid #3b3b3b
|
||||
+webapp-mobile
|
||||
font-size: 16px
|
||||
max-width: 100%
|
||||
|
||||
.bottom-bar
|
||||
border-radius: 0 0 5px 5px
|
||||
//background: #e3e3e3
|
||||
padding: 10px
|
||||
padding-top: 0
|
||||
display: flex
|
||||
justify-content: flex-end
|
||||
button
|
||||
font-size: 12px
|
||||
height: 28px
|
||||
border: 1px solid #c5c5c5
|
||||
border-radius: 3px
|
||||
padding-left: 10px
|
||||
padding-right: 10px
|
||||
&:focus
|
||||
outline-color: #48b57e
|
||||
+dark-mode
|
||||
background: #444
|
||||
border: none
|
||||
color: rgba(255,255,255, 0.75)
|
||||
|
||||
</style>
|
@ -5,6 +5,7 @@
|
||||
import { mapState, mapWritableState, mapActions } from 'pinia'
|
||||
import { useErrorStore } from "../stores/error-store"
|
||||
import { useNotesStore } from "../stores/notes-store"
|
||||
import { useEditorCacheStore } from "../stores/editor-cache"
|
||||
|
||||
const NUM_EDITOR_INSTANCES = 5
|
||||
|
||||
@ -45,10 +46,6 @@
|
||||
return {
|
||||
syntaxTreeDebugContent: null,
|
||||
editor: null,
|
||||
editorCache: {
|
||||
lru: [],
|
||||
cache: {}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@ -164,36 +161,21 @@
|
||||
|
||||
methods: {
|
||||
...mapActions(useErrorStore, ["addError"]),
|
||||
...mapActions(useEditorCacheStore, ["getEditor", "addEditor", "eachEditor"]),
|
||||
|
||||
loadBuffer(path) {
|
||||
console.log("loadBuffer", path)
|
||||
if (this.editor) {
|
||||
this.editor.hide()
|
||||
}
|
||||
|
||||
if (this.editorCache.cache[path]) {
|
||||
// editor is already loaded, just switch to it
|
||||
console.log("Switching to cached editor", path)
|
||||
toRaw(this.editor).hide()
|
||||
this.editor = this.editorCache.cache[path]
|
||||
let cachedEditor = this.getEditor(path)
|
||||
if (cachedEditor) {
|
||||
console.log("show cached editor")
|
||||
this.editor = cachedEditor
|
||||
toRaw(this.editor).show()
|
||||
//toRaw(this.editor).currenciesLoaded()
|
||||
this.currentEditor = toRaw(this.editor)
|
||||
window._heynote_editor = toRaw(this.editor)
|
||||
// move to end of LRU
|
||||
this.editorCache.lru = this.editorCache.lru.filter(p => p !== path)
|
||||
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)
|
||||
console.log("create new editor")
|
||||
try {
|
||||
this.editor = new HeynoteEditor({
|
||||
element: this.$refs.editor,
|
||||
@ -209,15 +191,15 @@
|
||||
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
|
||||
}
|
||||
this.addEditor(path, toRaw(this.editor))
|
||||
}
|
||||
|
||||
this.currentEditor = toRaw(this.editor)
|
||||
window._heynote_editor = toRaw(this.editor)
|
||||
},
|
||||
|
||||
setLanguage(language) {
|
||||
@ -245,10 +227,6 @@
|
||||
focus() {
|
||||
toRaw(this.editor).focus()
|
||||
},
|
||||
|
||||
eachEditor(fn) {
|
||||
Object.values(toRaw(this.editorCache).cache).forEach(fn)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -3,14 +3,16 @@
|
||||
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import { toRaw } from 'vue';
|
||||
import { useNotesStore } from "../stores/notes-store"
|
||||
import { useNotesStore, SCRATCH_FILE } from "../stores/notes-store"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selected: 0,
|
||||
actionButton: 0,
|
||||
filter: "",
|
||||
items: [],
|
||||
SCRATCH_FILE: SCRATCH_FILE,
|
||||
}
|
||||
},
|
||||
|
||||
@ -23,7 +25,7 @@
|
||||
"path": path,
|
||||
"name": metadata?.name || path,
|
||||
"folder": path.split("/").slice(0, -1).join("/"),
|
||||
"scratch": path === "buffer-dev.txt",
|
||||
"scratch": path === SCRATCH_FILE,
|
||||
}
|
||||
})
|
||||
if (this.items.length > 1) {
|
||||
@ -84,9 +86,11 @@
|
||||
methods: {
|
||||
...mapActions(useNotesStore, [
|
||||
"updateNotes",
|
||||
"editNote",
|
||||
]),
|
||||
|
||||
onKeydown(event) {
|
||||
const path = this.filteredItems[this.selected].path
|
||||
if (event.key === "ArrowDown") {
|
||||
if (this.selected === this.filteredItems.length - 1) {
|
||||
this.selected = 0
|
||||
@ -99,7 +103,7 @@
|
||||
} else {
|
||||
this.$refs.item[this.selected].scrollIntoView({block: "nearest"})
|
||||
}
|
||||
|
||||
this.actionButton = 0
|
||||
} else if (event.key === "ArrowUp") {
|
||||
if (this.selected === 0) {
|
||||
this.selected = this.filteredItems.length - 1
|
||||
@ -112,9 +116,23 @@
|
||||
} else {
|
||||
this.$refs.item[this.selected].scrollIntoView({block: "nearest"})
|
||||
}
|
||||
} else if (event.key === "Enter") {
|
||||
this.selectItem(this.filteredItems[this.selected].path)
|
||||
this.actionButton = 0
|
||||
} else if (event.key === "ArrowRight" && path !== SCRATCH_FILE) {
|
||||
event.preventDefault()
|
||||
this.actionButton = Math.min(2, this.actionButton + 1)
|
||||
} else if (event.key === "ArrowLeft" && path !== SCRATCH_FILE) {
|
||||
event.preventDefault()
|
||||
this.actionButton = Math.max(0, this.actionButton - 1)
|
||||
} else if (event.key === "Enter") {
|
||||
event.preventDefault()
|
||||
if (this.actionButton === 1) {
|
||||
console.log("edit file:", path)
|
||||
this.editNote(path)
|
||||
} else if (this.actionButton === 2) {
|
||||
console.log("delete file:", path)
|
||||
} else {
|
||||
this.selectItem(path)
|
||||
}
|
||||
} else if (event.key === "Escape") {
|
||||
this.$emit("close")
|
||||
event.preventDefault()
|
||||
@ -140,9 +158,16 @@
|
||||
getItemClass(item, idx) {
|
||||
return {
|
||||
"selected": idx === this.selected,
|
||||
"action-buttons-visible": this.actionButton > 0,
|
||||
"scratch": item.scratch,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
showActionButtons(idx) {
|
||||
this.selected = idx
|
||||
this.actionButton = 1
|
||||
this.$refs.input.focus()
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -167,6 +192,21 @@
|
||||
>
|
||||
<span class="name" v-html="item.name" />
|
||||
<span class="path" v-html="item.folder" />
|
||||
<span class="action-buttons">
|
||||
<button
|
||||
v-if="actionButton > 0 && idx === selected"
|
||||
:class="{'selected':actionButton === 1}"
|
||||
>Edit</button>
|
||||
<button
|
||||
v-if="actionButton > 0 && idx === selected"
|
||||
:class="{'delete':true, 'selected':actionButton === 2}"
|
||||
>Delete</button>
|
||||
<button
|
||||
class="show-actions"
|
||||
v-if="item.path !== SCRATCH_FILE && (actionButton === 0 || idx !== selected)"
|
||||
@click.stop.prevent="showActionButtons(idx)"
|
||||
></button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
@ -228,16 +268,20 @@
|
||||
.items
|
||||
overflow-y: auto
|
||||
> li
|
||||
position: relative
|
||||
border-radius: 3px
|
||||
padding: 5px 12px
|
||||
cursor: pointer
|
||||
display: flex
|
||||
align-items: center
|
||||
&:hover
|
||||
background: #e2e2e2
|
||||
.action-buttons .show-actions
|
||||
display: inline-block
|
||||
&.selected
|
||||
background: #48b57e
|
||||
color: #fff
|
||||
.action-buttons .show-actions
|
||||
display: inline-block
|
||||
&.scratch
|
||||
font-weight: 600
|
||||
+dark-mode
|
||||
@ -247,6 +291,10 @@
|
||||
&.selected
|
||||
background: #1b6540
|
||||
color: rgba(255,255,255, 0.87)
|
||||
&.action-buttons-visible
|
||||
background: none
|
||||
border: 1px solid #1b6540
|
||||
padding: 4px 11px
|
||||
.name
|
||||
margin-right: 12px
|
||||
flex-shrink: 0
|
||||
@ -264,4 +312,44 @@
|
||||
text-wrap: nowrap
|
||||
::v-deep(b)
|
||||
font-weight: 700
|
||||
.action-buttons
|
||||
position: absolute
|
||||
top: 1px
|
||||
right: 1px
|
||||
button
|
||||
padding: 1px 10px
|
||||
font-size: 12px
|
||||
background: none
|
||||
border: none
|
||||
border-radius: 2px
|
||||
margin-right: 2px
|
||||
cursor: pointer
|
||||
&:last-child
|
||||
margin-right: 0
|
||||
&:hover
|
||||
background: rgba(255,255,255, 0.1)
|
||||
+dark-mode
|
||||
//background: #1b6540
|
||||
//&:hover
|
||||
// background:
|
||||
&.selected
|
||||
background: #1b6540
|
||||
&:hover
|
||||
background: #1f7449
|
||||
&.delete
|
||||
background: #ae1e1e
|
||||
&:hover
|
||||
background: #bf2222
|
||||
&.show-actions
|
||||
display: none
|
||||
position: relative
|
||||
top: 1px
|
||||
padding: 1px 8px
|
||||
//cursor: default
|
||||
background-image: url(@/assets/icons/arrow-right.svg)
|
||||
width: 22px
|
||||
height: 19px
|
||||
background-size: 19px
|
||||
background-position: center center
|
||||
background-repeat: no-repeat
|
||||
</style>
|
||||
|
@ -217,6 +217,12 @@ export class HeynoteEditor {
|
||||
})
|
||||
}
|
||||
|
||||
setName(name) {
|
||||
this.note.metadata.name = name
|
||||
this.name = name
|
||||
triggerCursorChange(this.view)
|
||||
}
|
||||
|
||||
getBlocks() {
|
||||
return this.view.state.facet(blockState)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import App from './components/App.vue'
|
||||
import { loadCurrencies } from './currency'
|
||||
import { useErrorStore } from './stores/error-store'
|
||||
import { useNotesStore, initNotesStore } from './stores/notes-store'
|
||||
import { useEditorCacheStore } from './stores/editor-cache'
|
||||
|
||||
|
||||
const pinia = createPinia()
|
||||
@ -19,6 +20,7 @@ app.mount('#app').$nextTick(() => {
|
||||
})
|
||||
|
||||
const errorStore = useErrorStore()
|
||||
const editorCacheStore = useEditorCacheStore()
|
||||
//errorStore.addError("test error")
|
||||
window.heynote.getInitErrors().then((errors) => {
|
||||
errors.forEach((e) => errorStore.addError(e))
|
||||
|
48
src/stores/editor-cache.js
Normal file
48
src/stores/editor-cache.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { toRaw } from 'vue';
|
||||
import { defineStore } from "pinia"
|
||||
import { NoteFormat } from "../editor/note-format"
|
||||
|
||||
const NUM_EDITOR_INSTANCES = 5
|
||||
|
||||
export const useEditorCacheStore = defineStore("editorCache", {
|
||||
state: () => ({
|
||||
editorCache: {
|
||||
lru: [],
|
||||
cache: {},
|
||||
},
|
||||
}),
|
||||
|
||||
actions: {
|
||||
getEditor(path) {
|
||||
// move to end of LRU
|
||||
this.editorCache.lru = this.editorCache.lru.filter(p => p !== path)
|
||||
this.editorCache.lru.push(path)
|
||||
|
||||
if (this.editorCache.cache[path]) {
|
||||
return this.editorCache.cache[path]
|
||||
}
|
||||
},
|
||||
|
||||
addEditor(path, editor) {
|
||||
if (this.editorCache.lru.length >= NUM_EDITOR_INSTANCES) {
|
||||
const pathToFree = this.editorCache.lru.shift()
|
||||
this.freeEditor(pathToFree)
|
||||
}
|
||||
|
||||
this.editorCache.cache[path] = editor
|
||||
},
|
||||
|
||||
freeEditor(pathToFree) {
|
||||
if (!this.editorCache.cache[pathToFree]) {
|
||||
return
|
||||
}
|
||||
this.editorCache.cache[pathToFree].destroy()
|
||||
delete this.editorCache.cache[pathToFree]
|
||||
this.editorCache.lru = this.editorCache.lru.filter(p => p !== pathToFree)
|
||||
},
|
||||
|
||||
eachEditor(fn) {
|
||||
Object.values(this.editorCache.cache).forEach(fn)
|
||||
},
|
||||
},
|
||||
})
|
@ -1,8 +1,9 @@
|
||||
import { toRaw } from 'vue';
|
||||
import { defineStore } from "pinia"
|
||||
import { NoteFormat } from "../editor/note-format"
|
||||
import { useEditorCacheStore } from "./editor-cache"
|
||||
|
||||
const SCRATCH_FILE = window.heynote.isDev ? "buffer-dev.txt" : "buffer.txt"
|
||||
export const SCRATCH_FILE = window.heynote.isDev ? "buffer-dev.txt" : "buffer.txt"
|
||||
|
||||
export const useNotesStore = defineStore("notes", {
|
||||
state: () => ({
|
||||
@ -20,6 +21,7 @@ export const useNotesStore = defineStore("notes", {
|
||||
showNoteSelector: false,
|
||||
showLanguageSelector: false,
|
||||
showCreateNote: false,
|
||||
showEditNote: false,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
@ -32,9 +34,7 @@ export const useNotesStore = defineStore("notes", {
|
||||
},
|
||||
|
||||
openNote(path) {
|
||||
this.showNoteSelector = false
|
||||
this.showLanguageSelector = false
|
||||
this.showCreateNote = false
|
||||
this.closeDialog()
|
||||
this.currentNotePath = path
|
||||
|
||||
const recent = this.recentNotePaths.filter((p) => p !== path)
|
||||
@ -43,30 +43,49 @@ export const useNotesStore = defineStore("notes", {
|
||||
},
|
||||
|
||||
openLanguageSelector() {
|
||||
this.closeDialog()
|
||||
this.showLanguageSelector = true
|
||||
this.showNoteSelector = false
|
||||
this.showCreateNote = false
|
||||
},
|
||||
openNoteSelector() {
|
||||
this.closeDialog()
|
||||
this.showNoteSelector = true
|
||||
this.showLanguageSelector = false
|
||||
this.showCreateNote = false
|
||||
},
|
||||
openCreateNote() {
|
||||
this.closeDialog()
|
||||
this.showCreateNote = true
|
||||
this.showNoteSelector = false
|
||||
this.showLanguageSelector = false
|
||||
},
|
||||
closeDialog() {
|
||||
this.showCreateNote = false
|
||||
this.showNoteSelector = false
|
||||
this.showLanguageSelector = false
|
||||
this.showEditNote = false
|
||||
},
|
||||
|
||||
closeNoteSelector() {
|
||||
this.showNoteSelector = false
|
||||
},
|
||||
|
||||
editNote(path) {
|
||||
if (this.currentNotePath !== path) {
|
||||
this.openNote(path)
|
||||
}
|
||||
this.closeDialog()
|
||||
this.showEditNote = true
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new note file at `path` with name `name` from the current block of the current open editor
|
||||
*/
|
||||
async createNewNoteFromActiveBlock(path, name) {
|
||||
await toRaw(this.currentEditor).createNewNoteFromActiveBlock(path, name)
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new note file at path, with name `name`, and content content
|
||||
* @param {*} path: File path relative to Heynote root
|
||||
* @param {*} name Name of the note
|
||||
* @param {*} content Contents (without metadata)
|
||||
*/
|
||||
async saveNewNote(path, name, content) {
|
||||
//window.heynote.buffer.save(path, content)
|
||||
//this.updateNotes()
|
||||
@ -82,6 +101,24 @@ export const useNotesStore = defineStore("notes", {
|
||||
await window.heynote.buffer.create(path, note.serialize())
|
||||
this.updateNotes()
|
||||
},
|
||||
|
||||
async updateNoteMetadata(path, name, newPath) {
|
||||
const editorCacheStore = useEditorCacheStore()
|
||||
|
||||
if (this.currentEditor.path !== path) {
|
||||
throw new Error(`Can't update note (${path}) since it's not the active one (${this.currentEditor.path})`)
|
||||
}
|
||||
console.log("currentEditor", this.currentEditor)
|
||||
toRaw(this.currentEditor).setName(name)
|
||||
await (toRaw(this.currentEditor)).save()
|
||||
if (newPath && path !== newPath) {
|
||||
console.log("moving note", path, newPath)
|
||||
editorCacheStore.freeEditor(path)
|
||||
await window.heynote.buffer.move(path, newPath)
|
||||
this.openNote(newPath)
|
||||
this.updateNotes()
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user