diff --git a/out/main/index.js b/out/main/index.js
index 227a672..a9dd42b 100644
--- a/out/main/index.js
+++ b/out/main/index.js
@@ -1,7 +1,77 @@
"use strict";
-const electron = require("electron");
-const path = require("path");
const utils = require("@electron-toolkit/utils");
+const electron = require("electron");
+const fs = require("fs");
+const path = require("path");
+const BASE_DIR = path.join(electron.app.getPath("userData"), "notes-storage");
+const ensureBaseDir = () => {
+ if (!fs.existsSync(BASE_DIR)) {
+ fs.mkdirSync(BASE_DIR, { recursive: true });
+ }
+};
+const sanitizeRelativePath = (relativePath) => {
+ const resolved = path.join(BASE_DIR, relativePath);
+ if (!resolved.startsWith(BASE_DIR)) {
+ throw new Error("Invalid path");
+ }
+ return resolved;
+};
+const readAllNotesRecursive = (dir = BASE_DIR, base = BASE_DIR) => {
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
+ let results = [];
+ for (const entry of entries) {
+ const fullPath = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ results = results.concat(readAllNotesRecursive(fullPath, base));
+ }
+ if (entry.isFile() && entry.name.endsWith(".md")) {
+ const content = fs.readFileSync(fullPath, "utf-8");
+ results.push({
+ name: entry.name,
+ path: path.relative(base, fullPath),
+ content
+ });
+ }
+ }
+ return results;
+};
+const createNote = (relativePath, content = "") => {
+ const fullPath = sanitizeRelativePath(relativePath);
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
+ fs.writeFileSync(fullPath, content, "utf-8");
+ return true;
+};
+const createDirectory = (relativePath) => {
+ const fullPath = sanitizeRelativePath(relativePath);
+ fs.mkdirSync(fullPath, { recursive: true });
+ return true;
+};
+const readNote = (relativePath) => {
+ const fullPath = sanitizeRelativePath(relativePath);
+ if (!fs.existsSync(fullPath)) {
+ createNote(relativePath);
+ }
+ return fs.readFileSync(fullPath, "utf-8");
+};
+const updateNote = (relativePath, content) => {
+ const fullPath = sanitizeRelativePath(relativePath);
+ if (!fs.existsSync(fullPath)) {
+ throw new Error("Note does not exist");
+ }
+ fs.writeFileSync(fullPath, content, "utf-8");
+ return true;
+};
+const notes = {
+ ensureBaseDir,
+ sanitizeRelativePath,
+ readAllNotesRecursive,
+ createNote,
+ createDirectory,
+ readNote,
+ updateNote
+};
+const preloadPath = path.join(__dirname, "../preload/index.js");
+const rendererPath = path.join(__dirname, "../renderer/index.html");
function createWindow() {
const mainWindow2 = new electron.BrowserWindow({
width: 354,
@@ -9,7 +79,7 @@ function createWindow() {
show: false,
autoHideMenuBar: true,
webPreferences: {
- preload: path.join(__dirname, "../preload/index.js"),
+ preload: preloadPath,
sandbox: false
}
});
@@ -23,7 +93,7 @@ function createWindow() {
if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
mainWindow2.loadURL(process.env["ELECTRON_RENDERER_URL"]);
} else {
- mainWindow2.loadFile(path.join(__dirname, "../renderer/index.html"));
+ mainWindow2.loadFile(rendererPath);
}
}
function createNoteWindow(noteId) {
@@ -32,7 +102,7 @@ function createNoteWindow(noteId) {
height: 549,
autoHideMenuBar: true,
webPreferences: {
- preload: path.join(__dirname, "preload.js"),
+ preload: preloadPath,
contextIsolation: true,
nodeIntegration: false
}
@@ -42,7 +112,7 @@ function createNoteWindow(noteId) {
`${process.env["ELECTRON_RENDERER_URL"]}/note/${noteId}`
);
} else {
- mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"), {
+ mainWindow.loadFile(rendererPath, {
path: `/notes/${noteId}`
});
}
@@ -52,14 +122,29 @@ electron.app.whenReady().then(() => {
electron.app.on("browser-window-created", (_, window) => {
utils.optimizer.watchWindowShortcuts(window);
});
- console.log(electron.app.getPath("userData"));
createWindow();
+ notes.ensureBaseDir();
electron.app.on("activate", function() {
if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
});
electron.ipcMain.on("open-note-window", (_, noteId) => {
createNoteWindow(noteId);
});
+ electron.ipcMain.handle("notes:list", () => {
+ return notes.readAllNotesRecursive();
+ });
+ electron.ipcMain.handle("notes:create", (_, { path: path2, content }) => {
+ return notes.createNote(path2, content);
+ });
+ electron.ipcMain.handle("notes:createDir", (_, path2) => {
+ return notes.createDirectory(path2);
+ });
+ electron.ipcMain.handle("notes:read", (_, path2) => {
+ return notes.readNote(path2);
+ });
+ electron.ipcMain.handle("notes:update", (_, { path: path2, content }) => {
+ return notes.updateNote(path2, content);
+ });
});
electron.app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
diff --git a/out/preload/index.js b/out/preload/index.js
index cf9718e..fc9eb36 100644
--- a/out/preload/index.js
+++ b/out/preload/index.js
@@ -1,19 +1,21 @@
"use strict";
const electron = require("electron");
-const preload = require("@electron-toolkit/preload");
const api = {
openNoteWindow: (noteId) => {
electron.ipcRenderer.send("open-note-window", noteId);
- }
+ },
+ listNotes: () => electron.ipcRenderer.invoke("notes:list"),
+ createNote: (path, content) => electron.ipcRenderer.invoke("notes:create", { path, content }),
+ createNoteDir: (path) => electron.ipcRenderer.invoke("notes:createDir", path),
+ readNote: (path) => electron.ipcRenderer.invoke("notes:read", path),
+ updateNote: (path, content) => electron.ipcRenderer.invoke("notes:update", { path, content })
};
if (process.contextIsolated) {
try {
- electron.contextBridge.exposeInMainWorld("electron", preload.electronAPI);
electron.contextBridge.exposeInMainWorld("api", api);
} catch (error) {
console.error(error);
}
} else {
- window.electron = preload.electronAPI;
window.api = api;
}
diff --git a/package-lock.json b/package-lock.json
index 1cc4770..6abec00 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
"fecha": "^4.2.3",
"gsap": "^3.14.2",
"lenis": "^1.3.17",
+ "lodash": "^4.17.23",
"sass": "^1.97.3",
"sass-embedded": "^1.97.3",
"tempus": "^1.0.0-dev.17",
@@ -5945,7 +5946,6 @@
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
- "dev": true,
"license": "MIT"
},
"node_modules/lodash.escaperegexp": {
diff --git a/package.json b/package.json
index 0f7ca86..c20fc48 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"fecha": "^4.2.3",
"gsap": "^3.14.2",
"lenis": "^1.3.17",
+ "lodash": "^4.17.23",
"sass": "^1.97.3",
"sass-embedded": "^1.97.3",
"tempus": "^1.0.0-dev.17",
diff --git a/src/main/index.js b/src/main/index.js
index 0a704b5..655d0c5 100644
--- a/src/main/index.js
+++ b/src/main/index.js
@@ -1,6 +1,10 @@
-import { app, shell, BrowserWindow, ipcMain } from 'electron'
-import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
+import { app, shell, BrowserWindow, ipcMain } from 'electron'
+import notes from './notesStorage'
+import { join } from 'path'
+
+const preloadPath = join(__dirname, '../preload/index.js')
+const rendererPath = join(__dirname, '../renderer/index.html')
function createWindow() {
// Create the browser window.
@@ -10,7 +14,7 @@ function createWindow() {
show: false,
autoHideMenuBar: true,
webPreferences: {
- preload: join(__dirname, '../preload/index.js'),
+ preload: preloadPath,
sandbox: false,
},
})
@@ -29,7 +33,7 @@ function createWindow() {
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
- mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
+ mainWindow.loadFile(rendererPath)
}
}
@@ -39,7 +43,7 @@ function createNoteWindow(noteId) {
height: 549,
autoHideMenuBar: true,
webPreferences: {
- preload: join(__dirname, 'preload.js'),
+ preload: preloadPath,
contextIsolation: true,
nodeIntegration: false,
},
@@ -50,7 +54,7 @@ function createNoteWindow(noteId) {
`${process.env['ELECTRON_RENDERER_URL']}/note/${noteId}`,
)
} else {
- mainWindow.loadFile(join(__dirname, '../renderer/index.html'), {
+ mainWindow.loadFile(rendererPath, {
path: `/notes/${noteId}`,
})
}
@@ -70,10 +74,12 @@ app.whenReady().then(() => {
optimizer.watchWindowShortcuts(window)
})
- console.log(app.getPath('userData'))
-
+ // Create main window
createWindow()
+ // Ensure data directory is present
+ notes.ensureBaseDir()
+
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
@@ -84,6 +90,23 @@ app.whenReady().then(() => {
ipcMain.on('open-note-window', (_, noteId) => {
createNoteWindow(noteId)
})
+
+ // File access
+ ipcMain.handle('notes:list', () => {
+ return notes.readAllNotesRecursive()
+ })
+ ipcMain.handle('notes:create', (_, { path, content }) => {
+ return notes.createNote(path, content)
+ })
+ ipcMain.handle('notes:createDir', (_, path) => {
+ return notes.createDirectory(path)
+ })
+ ipcMain.handle('notes:read', (_, path) => {
+ return notes.readNote(path)
+ })
+ ipcMain.handle('notes:update', (_, { path, content }) => {
+ return notes.updateNote(path, content)
+ })
})
// Quit when all windows are closed, except on macOS. There, it's common
diff --git a/src/main/notesStorage.js b/src/main/notesStorage.js
new file mode 100644
index 0000000..395e265
--- /dev/null
+++ b/src/main/notesStorage.js
@@ -0,0 +1,91 @@
+import { app } from 'electron'
+import fs from 'fs'
+import { join, relative, dirname } from 'path'
+
+const BASE_DIR = join(app.getPath('userData'), 'notes-storage')
+
+export const ensureBaseDir = () => {
+ if (!fs.existsSync(BASE_DIR)) {
+ fs.mkdirSync(BASE_DIR, { recursive: true })
+ }
+}
+
+export const sanitizeRelativePath = (relativePath) => {
+ const resolved = join(BASE_DIR, relativePath)
+ if (!resolved.startsWith(BASE_DIR)) {
+ throw new Error('Invalid path')
+ }
+ return resolved
+}
+
+export const readAllNotesRecursive = (dir = BASE_DIR, base = BASE_DIR) => {
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
+ let results = []
+
+ for (const entry of entries) {
+ const fullPath = join(dir, entry.name)
+
+ if (entry.isDirectory()) {
+ results = results.concat(readAllNotesRecursive(fullPath, base))
+ }
+
+ if (entry.isFile() && entry.name.endsWith('.md')) {
+ const content = fs.readFileSync(fullPath, 'utf-8')
+
+ results.push({
+ name: entry.name,
+ path: relative(base, fullPath),
+ content,
+ })
+ }
+ }
+
+ return results
+}
+
+export const createNote = (relativePath, content = '') => {
+ const fullPath = sanitizeRelativePath(relativePath)
+
+ fs.mkdirSync(dirname(fullPath), { recursive: true })
+ fs.writeFileSync(fullPath, content, 'utf-8')
+
+ return true
+}
+
+export const createDirectory = (relativePath) => {
+ const fullPath = sanitizeRelativePath(relativePath)
+ fs.mkdirSync(fullPath, { recursive: true })
+ return true
+}
+
+export const readNote = (relativePath) => {
+ const fullPath = sanitizeRelativePath(relativePath)
+
+ if (!fs.existsSync(fullPath)) {
+ createNote(relativePath)
+ }
+
+ return fs.readFileSync(fullPath, 'utf-8')
+}
+
+export const updateNote = (relativePath, content) => {
+ const fullPath = sanitizeRelativePath(relativePath)
+
+ if (!fs.existsSync(fullPath)) {
+ throw new Error('Note does not exist')
+ }
+
+ fs.writeFileSync(fullPath, content, 'utf-8')
+
+ return true
+}
+
+export default {
+ ensureBaseDir,
+ sanitizeRelativePath,
+ readAllNotesRecursive,
+ createNote,
+ createDirectory,
+ readNote,
+ updateNote,
+}
diff --git a/src/preload/index.js b/src/preload/index.js
index 3b8fa3e..12d7114 100644
--- a/src/preload/index.js
+++ b/src/preload/index.js
@@ -1,24 +1,25 @@
import { contextBridge, ipcRenderer } from 'electron'
-import { electronAPI } from '@electron-toolkit/preload'
// Custom APIs for renderer
const api = {
openNoteWindow: (noteId) => {
ipcRenderer.send('open-note-window', noteId)
},
+ listNotes: () => ipcRenderer.invoke('notes:list'),
+ createNote: (path, content) =>
+ ipcRenderer.invoke('notes:create', { path, content }),
+ createNoteDir: (path) => ipcRenderer.invoke('notes:createDir', path),
+ readNote: (path) => ipcRenderer.invoke('notes:read', path),
+ updateNote: (path, content) =>
+ ipcRenderer.invoke('notes:update', { path, content }),
}
-// Use `contextBridge` APIs to expose Electron APIs to
-// renderer only if context isolation is enabled, otherwise
-// just add to the DOM global.
if (process.contextIsolated) {
try {
- contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
- window.electron = electronAPI
window.api = api
}
diff --git a/src/renderer/src/composables/useNotes.js b/src/renderer/src/composables/useNotes.js
new file mode 100644
index 0000000..678c927
--- /dev/null
+++ b/src/renderer/src/composables/useNotes.js
@@ -0,0 +1,48 @@
+import { ref } from 'vue'
+
+export default () => {
+ const notes = ref([])
+ const loading = ref(false)
+ const error = ref(null)
+
+ const fetchNotes = async () => {
+ try {
+ loading.value = true
+ notes.value = await window.api.listNotes()
+ } catch (err) {
+ error.value = err.message
+ } finally {
+ loading.value = false
+ }
+ }
+
+ const createNote = async (path, content = '') => {
+ await window.api.createNote(path, content)
+ await fetchNotes()
+ }
+
+ const createDirectory = async (path) => {
+ await window.api.createNoteDir(path)
+ await fetchNotes()
+ }
+
+ const readNote = async (path) => {
+ console.log(path)
+ return await window.api.readNote(path)
+ }
+
+ const updateNote = async (path, content) => {
+ return await window.api.updateNote(path, content)
+ }
+
+ return {
+ notes,
+ loading,
+ error,
+ fetchNotes,
+ createNote,
+ createDirectory,
+ readNote,
+ updateNote,
+ }
+}
diff --git a/src/renderer/src/views/Directory.vue b/src/renderer/src/views/Directory.vue
index 47759aa..c329176 100644
--- a/src/renderer/src/views/Directory.vue
+++ b/src/renderer/src/views/Directory.vue
@@ -1,10 +1,6 @@
-