Add "command palette" functionality

This commit is contained in:
Jonatan Heyman 2025-04-11 00:10:29 +02:00
parent 85b91f0228
commit addf310ae1
6 changed files with 96 additions and 24 deletions

View File

@ -65,6 +65,7 @@
showCreateBuffer(value) { this.dialogWatcher(value) }, showCreateBuffer(value) { this.dialogWatcher(value) },
showEditBuffer(value) { this.dialogWatcher(value) }, showEditBuffer(value) { this.dialogWatcher(value) },
showMoveToBufferSelector(value) { this.dialogWatcher(value) }, showMoveToBufferSelector(value) { this.dialogWatcher(value) },
showCommandPalette(value) { this.dialogWatcher(value) },
currentBufferPath() { currentBufferPath() {
this.focusEditor() this.focusEditor()
@ -85,10 +86,11 @@
"showCreateBuffer", "showCreateBuffer",
"showEditBuffer", "showEditBuffer",
"showMoveToBufferSelector", "showMoveToBufferSelector",
"showCommandPalette",
]), ]),
dialogVisible() { dialogVisible() {
return this.showLanguageSelector || this.showBufferSelector || this.showCreateBuffer || this.showEditBuffer || this.showMoveToBufferSelector return this.showLanguageSelector || this.showBufferSelector || this.showCreateBuffer || this.showEditBuffer || this.showMoveToBufferSelector || this.showCommandPalette
}, },
editorInert() { editorInert() {
@ -176,7 +178,9 @@
@close="closeDialog" @close="closeDialog"
/> />
<BufferSelector <BufferSelector
v-if="showBufferSelector" v-if="showBufferSelector || showCommandPalette"
:initialFilter="showCommandPalette ? '>' : ''"
:commandsEnabled="true"
@openBuffer="openBuffer" @openBuffer="openBuffer"
@openCreateBuffer="(nameSuggestion) => openCreateBuffer('new', nameSuggestion)" @openCreateBuffer="(nameSuggestion) => openCreateBuffer('new', nameSuggestion)"
@close="closeBufferSelector" @close="closeBufferSelector"
@ -184,6 +188,7 @@
<BufferSelector <BufferSelector
v-if="showMoveToBufferSelector" v-if="showMoveToBufferSelector"
headline="Move block to..." headline="Move block to..."
:commandsEnabled="false"
@openBuffer="onMoveCurrentBlockToOtherEditor" @openBuffer="onMoveCurrentBlockToOtherEditor"
@openCreateBuffer="(nameSuggestion) => openCreateBuffer('currentBlock', nameSuggestion)" @openCreateBuffer="(nameSuggestion) => openCreateBuffer('currentBlock', nameSuggestion)"
@close="closeMoveToBufferSelector" @close="closeMoveToBufferSelector"

View File

@ -4,6 +4,7 @@
import { mapState, mapActions } from 'pinia' import { mapState, mapActions } from 'pinia'
import { SCRATCH_FILE_NAME } from "../common/constants" import { SCRATCH_FILE_NAME } from "../common/constants"
import { useHeynoteStore } from "../stores/heynote-store" import { useHeynoteStore } from "../stores/heynote-store"
import { HEYNOTE_COMMANDS } from '../editor/commands'
const pathSep = window.heynote.buffer.pathSeparator const pathSep = window.heynote.buffer.pathSeparator
@ -19,13 +20,15 @@
export default { export default {
props: { props: {
headline: String, headline: String,
initialFilter: String,
commandsEnabled: Boolean,
}, },
data() { data() {
return { return {
selected: 0, selected: 0,
actionButton: 0, actionButton: 0,
filter: "", filter: this.initialFilter || "",
items: [], items: [],
SCRATCH_FILE_NAME: SCRATCH_FILE_NAME, SCRATCH_FILE_NAME: SCRATCH_FILE_NAME,
deleteConfirm: false, deleteConfirm: false,
@ -48,6 +51,14 @@
"recentBufferPaths", "recentBufferPaths",
]), ]),
commands() {
return Object.keys(HEYNOTE_COMMANDS).map(cmd => ({
name: cmd,
cmd: cmd,
isCommand: true,
}))
},
orderedItems() { orderedItems() {
const sortKeys = Object.fromEntries(this.recentBufferPaths.map((item, idx) => [item, idx])) const sortKeys = Object.fromEntries(this.recentBufferPaths.map((item, idx) => [item, idx]))
const getSortScore = (item) => sortKeys[item.path] !== undefined ? sortKeys[item.path] : 1000 const getSortScore = (item) => sortKeys[item.path] !== undefined ? sortKeys[item.path] : 1000
@ -74,32 +85,47 @@
}, },
filteredItems() { filteredItems() {
let items if (this.commandsEnabled && this.filter.startsWith(">")) {
if (this.filter === "") { // command mode if the first character is ">"
items = this.orderedItems if (this.filter.length < 2) {
return this.commands
} else { }
const searchResults = fuzzysort.go(this.filter, this.items, { const searchResults = fuzzysort.go(this.filter.slice(1), this.commands, {
keys: ["name", "folder"], keys: ["name"],
}) })
items = searchResults.map((result) => { return searchResults.map((result) => {
const obj = {...result.obj} const obj = {...result.obj}
const nameHighlight = result[0].highlight("<b>", "</b>") const nameHighlight = result[0].highlight("<b>", "</b>")
const folderHighlight = result[1].highlight("<b>", "</b>")
obj.name = nameHighlight !== "" ? nameHighlight : obj.name obj.name = nameHighlight !== "" ? nameHighlight : obj.name
obj.folder = folderHighlight !== "" ? folderHighlight : obj.folder
return obj return obj
}) })
} else {
let items
if (this.filter === "") {
items = this.orderedItems
} else {
const searchResults = fuzzysort.go(this.filter, this.items, {
keys: ["name", "folder"],
})
items = searchResults.map((result) => {
const obj = {...result.obj}
const nameHighlight = result[0].highlight("<b>", "</b>")
const folderHighlight = result[1].highlight("<b>", "</b>")
obj.name = nameHighlight !== "" ? nameHighlight : obj.name
obj.folder = folderHighlight !== "" ? folderHighlight : obj.folder
return obj
})
}
const newNoteItem = {
name: "Create new…",
createNew:true,
}
return [
...items,
newNoteItem,
]
} }
const newNoteItem = {
name: "Create new…",
createNew:true,
}
return [
...items,
newNoteItem,
]
}, },
}, },
@ -108,6 +134,7 @@
"updateBuffers", "updateBuffers",
"editBufferMetadata", "editBufferMetadata",
"deleteBuffer", "deleteBuffer",
"executeCommand",
]), ]),
buildItems() { buildItems() {
@ -187,13 +214,18 @@
} else { } else {
this.$emit("openCreateBuffer", "") this.$emit("openCreateBuffer", "")
} }
} else if (item.isCommand) {
this.$emit("close")
this.$nextTick(() => {
this.executeCommand(item.cmd)
})
} else { } else {
this.$emit("openBuffer", item.path) this.$emit("openBuffer", item.path)
} }
}, },
itemHasActionButtons(item) { itemHasActionButtons(item) {
return !item.createNew && item.path !== SCRATCH_FILE_NAME return !item.createNew && item.path !== SCRATCH_FILE_NAME && !item.isCommand
}, },
onInput(event) { onInput(event) {
@ -248,7 +280,7 @@
</script> </script>
<template> <template>
<form class="note-selector" tabindex="-1" @focusout="onFocusOut" ref="container"> <form class="note-selector" tabindex="-1" @focusout="onFocusOut" ref="container" @submit.prevent>
<div class="input-container"> <div class="input-container">
<h1 v-if="headline">{{headline}}</h1> <h1 v-if="headline">{{headline}}</h1>
<input <input

View File

@ -46,6 +46,10 @@ const openBufferSelector = (editor) => () => {
editor.openBufferSelector() editor.openBufferSelector()
return true return true
} }
const openCommandPalette = (editor) => () => {
editor.openCommandPalette()
return true
}
const openMoveToBuffer = (editor) => () => { const openMoveToBuffer = (editor) => () => {
editor.openMoveToBufferSelector() editor.openMoveToBufferSelector()
return true return true
@ -69,6 +73,7 @@ const HEYNOTE_COMMANDS = {
openLanguageSelector, openLanguageSelector,
openBufferSelector, openBufferSelector,
openCommandPalette,
openMoveToBuffer, openMoveToBuffer,
openCreateNewBuffer, openCreateNewBuffer,

View File

@ -22,6 +22,7 @@ import { languageDetection } from "./language-detection/autodetect.js"
import { autoSaveContent } from "./save.js" import { autoSaveContent } from "./save.js"
import { todoCheckboxPlugin} from "./todo-checkbox.ts" import { todoCheckboxPlugin} from "./todo-checkbox.ts"
import { links } from "./links.js" import { links } from "./links.js"
import { HEYNOTE_COMMANDS } from "./commands.js";
import { NoteFormat } from "../common/note-format.js" import { NoteFormat } from "../common/note-format.js"
import { AUTO_SAVE_INTERVAL } from "../common/constants.js" import { AUTO_SAVE_INTERVAL } from "../common/constants.js"
import { useHeynoteStore } from "../stores/heynote-store.js"; import { useHeynoteStore } from "../stores/heynote-store.js";
@ -294,6 +295,10 @@ export class HeynoteEditor {
this.notesStore.openBufferSelector() this.notesStore.openBufferSelector()
} }
openCommandPalette() {
this.notesStore.openCommandPalette()
}
openCreateBuffer(createMode) { openCreateBuffer(createMode) {
this.notesStore.openCreateBuffer(createMode) this.notesStore.openCreateBuffer(createMode)
} }
@ -428,6 +433,15 @@ export class HeynoteEditor {
selectAll() { selectAll() {
selectAll(this.view) selectAll(this.view)
} }
executeCommand(command) {
const cmd = HEYNOTE_COMMANDS[command]
if (!cmd) {
console.error(`Command not found: ${command}`)
return
}
cmd(this)(this.view)
}
} }

View File

@ -104,6 +104,7 @@ export const DEFAULT_KEYMAP = [
cmd("Mod-l", "openLanguageSelector"), cmd("Mod-l", "openLanguageSelector"),
cmd("Mod-p", "openBufferSelector"), cmd("Mod-p", "openBufferSelector"),
cmd("Mod-Shift-p", "openCommandPalette"),
cmd("Mod-s", "openMoveToBuffer"), cmd("Mod-s", "openMoveToBuffer"),
cmd("Mod-n", "openCreateNewBuffer"), cmd("Mod-n", "openCreateNewBuffer"),

View File

@ -28,6 +28,7 @@ export const useHeynoteStore = defineStore("heynote", {
showCreateBuffer: false, showCreateBuffer: false,
showEditBuffer: false, showEditBuffer: false,
showMoveToBufferSelector: false, showMoveToBufferSelector: false,
showCommandPalette: false,
}), }),
actions: { actions: {
@ -58,6 +59,11 @@ export const useHeynoteStore = defineStore("heynote", {
openBufferSelector() { openBufferSelector() {
this.closeDialog() this.closeDialog()
this.showBufferSelector = true this.showBufferSelector = true
},
openCommandPalette() {
this.closeDialog()
this.showCommandPalette = true
}, },
openMoveToBufferSelector() { openMoveToBufferSelector() {
this.closeDialog() this.closeDialog()
@ -78,10 +84,12 @@ export const useHeynoteStore = defineStore("heynote", {
this.showLanguageSelector = false this.showLanguageSelector = false
this.showEditBuffer = false this.showEditBuffer = false
this.showMoveToBufferSelector = false this.showMoveToBufferSelector = false
this.showCommandPalette = false
}, },
closeBufferSelector() { closeBufferSelector() {
this.showBufferSelector = false this.showBufferSelector = false
this.showCommandPalette = false
}, },
closeMoveToBufferSelector() { closeMoveToBufferSelector() {
@ -96,6 +104,13 @@ export const useHeynoteStore = defineStore("heynote", {
this.showEditBuffer = true this.showEditBuffer = true
}, },
executeCommand(command) {
if (this.currentEditor) {
toRaw(this.currentEditor).executeCommand(command)
}
},
/** /**
* Create a new note file at `path` with name `name` from the current block of the current open editor, * Create a new note file at `path` with name `name` from the current block of the current open editor,
* and switch to it * and switch to it