From 5e34656c1d81b0e948950a11829ba9ac92fa59cc Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Thu, 25 Jul 2024 13:25:19 +0200 Subject: [PATCH] WIP: Create new node dialog Implement folder selector UI element. Retrieve actual folder structure from Notes library. --- electron/main/file-library.js | 15 +- electron/preload/index.ts | 4 + src/components/NewNote.vue | 77 +++++- src/components/folder-selector/FolderItem.vue | 96 ++++++++ .../folder-selector/FolderSelector.vue | 231 ++++++++++++++++++ .../folder-selector/NewFolderItem.vue | 108 ++++++++ .../folder-selector/sanitize-filename.js | 14 ++ src/components/form/FolderSelect.vue | 53 ---- webapp/bridge.js | 4 + 9 files changed, 542 insertions(+), 60 deletions(-) create mode 100644 src/components/folder-selector/FolderItem.vue create mode 100644 src/components/folder-selector/FolderSelector.vue create mode 100644 src/components/folder-selector/NewFolderItem.vue create mode 100644 src/components/folder-selector/sanitize-filename.js delete mode 100644 src/components/form/FolderSelect.vue diff --git a/electron/main/file-library.js b/electron/main/file-library.js index e1bb7f1..4a8dd94 100644 --- a/electron/main/file-library.js +++ b/electron/main/file-library.js @@ -67,7 +67,7 @@ export class FileLibrary { async getList() { console.log("Loading notes") const notes = {} - const files = await this.jetpack.findAsync(this.basePath, { + const files = await this.jetpack.findAsync(".", { matching: "*.txt", recursive: true, }) @@ -83,6 +83,15 @@ export class FileLibrary { return notes } + async getDirectoryList() { + const directories = await this.jetpack.findAsync("", { + files: false, + directories: true, + recursive: true, + }) + return directories + } + setupWatcher(win) { if (!this.watcher) { this.watcher = fs.watch( @@ -189,6 +198,10 @@ export function setupFileLibraryEventHandlers(library, win) { return await library.getList() }); + ipcMain.handle('buffer:getDirectoryList', async (event) => { + return await library.getDirectoryList() + }); + ipcMain.handle('buffer:exists', async (event, path) => { return await library.exists(path) }); diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 45baa60..5bd74c3 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -65,6 +65,10 @@ contextBridge.exposeInMainWorld("heynote", { return await ipcRenderer.invoke("buffer:getList") }, + async getDirectoryList() { + return await ipcRenderer.invoke("buffer:getDirectoryList") + }, + async load(path) { return await ipcRenderer.invoke("buffer:load", path) }, diff --git a/src/components/NewNote.vue b/src/components/NewNote.vue index e95a9aa..4122970 100644 --- a/src/components/NewNote.vue +++ b/src/components/NewNote.vue @@ -2,7 +2,7 @@ import { mapState, mapActions } from 'pinia' import { useNotesStore } from "../stores/notes-store" - import FolderSelect from './form/FolderSelect.vue' + import FolderSelector from './folder-selector/FolderSelector.vue' export default { data() { @@ -10,21 +10,60 @@ name: "", filename: "", tags: [], + directoryTree: null, + parentPath: "", } }, components: { - FolderSelect + FolderSelector }, async mounted() { - await this.updateNotes() this.$refs.nameInput.focus() + this.updateNotes() + + // build directory tree + const directories = await window.heynote.buffer.getDirectoryList() + const rootNode = { + name: "Heynote Root", + path: "", + children: [], + } + 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 { + node = { + name: part, + children: [], + path: currentParts.join("/"), + } + currentLevel.children.push(node) + currentLevel = node + } + }) + }) + //console.log("tree:", rootNode) + this.directoryTree = rootNode }, computed: { ...mapState(useNotesStore, [ "notes", + "currentNotePath", ]), + + currentNoteDirectory() { + return this.currentNotePath.split("/").slice(0, -1).join("/") + }, }, methods: { @@ -39,6 +78,15 @@ } }, + 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() + } + }, + onSubmit(event) { event.preventDefault() console.log("Creating note", this.name) @@ -60,10 +108,18 @@ v-model="name" class="name-input" ref="nameInput" + @keydown="onInputKeydown" /> - +
@@ -84,12 +140,16 @@ 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 @@ -101,6 +161,9 @@ .container padding: 10px + min-height: 0 + display: flex + flex-direction: column h1 font-weight: bold @@ -115,7 +178,7 @@ .name-input - width: 400px + width: 100% background: #fff padding: 4px 5px border: 1px solid #ccc @@ -138,8 +201,9 @@ .bottom-bar border-radius: 0 0 5px 5px - background: #e3e3e3 + //background: #e3e3e3 padding: 10px + padding-top: 0 display: flex justify-content: flex-end +dark-mode @@ -155,3 +219,4 @@ outline-color: #48b57e +./folder-selector/FolderSelector.vue \ No newline at end of file diff --git a/src/components/folder-selector/FolderItem.vue b/src/components/folder-selector/FolderItem.vue new file mode 100644 index 0000000..f321095 --- /dev/null +++ b/src/components/folder-selector/FolderItem.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/components/folder-selector/FolderSelector.vue b/src/components/folder-selector/FolderSelector.vue new file mode 100644 index 0000000..f1e4dbe --- /dev/null +++ b/src/components/folder-selector/FolderSelector.vue @@ -0,0 +1,231 @@ + + + + + diff --git a/src/components/folder-selector/NewFolderItem.vue b/src/components/folder-selector/NewFolderItem.vue new file mode 100644 index 0000000..5cafd14 --- /dev/null +++ b/src/components/folder-selector/NewFolderItem.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/src/components/folder-selector/sanitize-filename.js b/src/components/folder-selector/sanitize-filename.js new file mode 100644 index 0000000..7693e7c --- /dev/null +++ b/src/components/folder-selector/sanitize-filename.js @@ -0,0 +1,14 @@ +const illegalRe = /[\/\?<>\\:\*\|"]/g; +const controlRe = /[\x00-\x1f\x80-\x9f]/g; +const reservedRe = /^\.+$/; +const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i; +const windowsTrailingRe = /[\. ]+$/; + +export default function sanitizeFilename(input, replacement) { + return input.trim() + .replace(illegalRe, replacement) + .replace(controlRe, replacement) + .replace(reservedRe, replacement) + .replace(windowsReservedRe, replacement) + .replace(windowsTrailingRe, replacement) +} diff --git a/src/components/form/FolderSelect.vue b/src/components/form/FolderSelect.vue deleted file mode 100644 index e3d2881..0000000 --- a/src/components/form/FolderSelect.vue +++ /dev/null @@ -1,53 +0,0 @@ - - - - - diff --git a/webapp/bridge.js b/webapp/bridge.js index 107a955..2cf646f 100644 --- a/webapp/bridge.js +++ b/webapp/bridge.js @@ -103,6 +103,10 @@ const Heynote = { return [{"path":"buffer.txt", "metadata":{}}] }, + async getDirectoryList() { + return [] + }, + async close(path) { },