local file saving/reading
This commit is contained in:
@@ -1,7 +1,77 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
const electron = require("electron");
|
|
||||||
const path = require("path");
|
|
||||||
const utils = require("@electron-toolkit/utils");
|
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() {
|
function createWindow() {
|
||||||
const mainWindow2 = new electron.BrowserWindow({
|
const mainWindow2 = new electron.BrowserWindow({
|
||||||
width: 354,
|
width: 354,
|
||||||
@@ -9,7 +79,7 @@ function createWindow() {
|
|||||||
show: false,
|
show: false,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: path.join(__dirname, "../preload/index.js"),
|
preload: preloadPath,
|
||||||
sandbox: false
|
sandbox: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -23,7 +93,7 @@ function createWindow() {
|
|||||||
if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||||
mainWindow2.loadURL(process.env["ELECTRON_RENDERER_URL"]);
|
mainWindow2.loadURL(process.env["ELECTRON_RENDERER_URL"]);
|
||||||
} else {
|
} else {
|
||||||
mainWindow2.loadFile(path.join(__dirname, "../renderer/index.html"));
|
mainWindow2.loadFile(rendererPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function createNoteWindow(noteId) {
|
function createNoteWindow(noteId) {
|
||||||
@@ -32,7 +102,7 @@ function createNoteWindow(noteId) {
|
|||||||
height: 549,
|
height: 549,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: path.join(__dirname, "preload.js"),
|
preload: preloadPath,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
nodeIntegration: false
|
nodeIntegration: false
|
||||||
}
|
}
|
||||||
@@ -42,7 +112,7 @@ function createNoteWindow(noteId) {
|
|||||||
`${process.env["ELECTRON_RENDERER_URL"]}/note/${noteId}`
|
`${process.env["ELECTRON_RENDERER_URL"]}/note/${noteId}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"), {
|
mainWindow.loadFile(rendererPath, {
|
||||||
path: `/notes/${noteId}`
|
path: `/notes/${noteId}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -52,14 +122,29 @@ electron.app.whenReady().then(() => {
|
|||||||
electron.app.on("browser-window-created", (_, window) => {
|
electron.app.on("browser-window-created", (_, window) => {
|
||||||
utils.optimizer.watchWindowShortcuts(window);
|
utils.optimizer.watchWindowShortcuts(window);
|
||||||
});
|
});
|
||||||
console.log(electron.app.getPath("userData"));
|
|
||||||
createWindow();
|
createWindow();
|
||||||
|
notes.ensureBaseDir();
|
||||||
electron.app.on("activate", function() {
|
electron.app.on("activate", function() {
|
||||||
if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
|
if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||||
});
|
});
|
||||||
electron.ipcMain.on("open-note-window", (_, noteId) => {
|
electron.ipcMain.on("open-note-window", (_, noteId) => {
|
||||||
createNoteWindow(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", () => {
|
electron.app.on("window-all-closed", () => {
|
||||||
if (process.platform !== "darwin") {
|
if (process.platform !== "darwin") {
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
const electron = require("electron");
|
const electron = require("electron");
|
||||||
const preload = require("@electron-toolkit/preload");
|
|
||||||
const api = {
|
const api = {
|
||||||
openNoteWindow: (noteId) => {
|
openNoteWindow: (noteId) => {
|
||||||
electron.ipcRenderer.send("open-note-window", 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) {
|
if (process.contextIsolated) {
|
||||||
try {
|
try {
|
||||||
electron.contextBridge.exposeInMainWorld("electron", preload.electronAPI);
|
|
||||||
electron.contextBridge.exposeInMainWorld("api", api);
|
electron.contextBridge.exposeInMainWorld("api", api);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window.electron = preload.electronAPI;
|
|
||||||
window.api = api;
|
window.api = api;
|
||||||
}
|
}
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -23,6 +23,7 @@
|
|||||||
"fecha": "^4.2.3",
|
"fecha": "^4.2.3",
|
||||||
"gsap": "^3.14.2",
|
"gsap": "^3.14.2",
|
||||||
"lenis": "^1.3.17",
|
"lenis": "^1.3.17",
|
||||||
|
"lodash": "^4.17.23",
|
||||||
"sass": "^1.97.3",
|
"sass": "^1.97.3",
|
||||||
"sass-embedded": "^1.97.3",
|
"sass-embedded": "^1.97.3",
|
||||||
"tempus": "^1.0.0-dev.17",
|
"tempus": "^1.0.0-dev.17",
|
||||||
@@ -5945,7 +5946,6 @@
|
|||||||
"version": "4.17.23",
|
"version": "4.17.23",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.escaperegexp": {
|
"node_modules/lodash.escaperegexp": {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"fecha": "^4.2.3",
|
"fecha": "^4.2.3",
|
||||||
"gsap": "^3.14.2",
|
"gsap": "^3.14.2",
|
||||||
"lenis": "^1.3.17",
|
"lenis": "^1.3.17",
|
||||||
|
"lodash": "^4.17.23",
|
||||||
"sass": "^1.97.3",
|
"sass": "^1.97.3",
|
||||||
"sass-embedded": "^1.97.3",
|
"sass-embedded": "^1.97.3",
|
||||||
"tempus": "^1.0.0-dev.17",
|
"tempus": "^1.0.0-dev.17",
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { app, shell, BrowserWindow, ipcMain } from 'electron'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
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() {
|
function createWindow() {
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
@@ -10,7 +14,7 @@ function createWindow() {
|
|||||||
show: false,
|
show: false,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: join(__dirname, '../preload/index.js'),
|
preload: preloadPath,
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -29,7 +33,7 @@ function createWindow() {
|
|||||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||||
} else {
|
} else {
|
||||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
|
mainWindow.loadFile(rendererPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +43,7 @@ function createNoteWindow(noteId) {
|
|||||||
height: 549,
|
height: 549,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: join(__dirname, 'preload.js'),
|
preload: preloadPath,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
},
|
},
|
||||||
@@ -50,7 +54,7 @@ function createNoteWindow(noteId) {
|
|||||||
`${process.env['ELECTRON_RENDERER_URL']}/note/${noteId}`,
|
`${process.env['ELECTRON_RENDERER_URL']}/note/${noteId}`,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html'), {
|
mainWindow.loadFile(rendererPath, {
|
||||||
path: `/notes/${noteId}`,
|
path: `/notes/${noteId}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -70,10 +74,12 @@ app.whenReady().then(() => {
|
|||||||
optimizer.watchWindowShortcuts(window)
|
optimizer.watchWindowShortcuts(window)
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(app.getPath('userData'))
|
// Create main window
|
||||||
|
|
||||||
createWindow()
|
createWindow()
|
||||||
|
|
||||||
|
// Ensure data directory is present
|
||||||
|
notes.ensureBaseDir()
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
// On macOS it's common to re-create a window in the app when the
|
// 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.
|
// dock icon is clicked and there are no other windows open.
|
||||||
@@ -84,6 +90,23 @@ app.whenReady().then(() => {
|
|||||||
ipcMain.on('open-note-window', (_, noteId) => {
|
ipcMain.on('open-note-window', (_, noteId) => {
|
||||||
createNoteWindow(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
|
// Quit when all windows are closed, except on macOS. There, it's common
|
||||||
|
|||||||
91
src/main/notesStorage.js
Normal file
91
src/main/notesStorage.js
Normal file
@@ -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,
|
||||||
|
}
|
||||||
@@ -1,24 +1,25 @@
|
|||||||
import { contextBridge, ipcRenderer } from 'electron'
|
import { contextBridge, ipcRenderer } from 'electron'
|
||||||
import { electronAPI } from '@electron-toolkit/preload'
|
|
||||||
|
|
||||||
// Custom APIs for renderer
|
// Custom APIs for renderer
|
||||||
const api = {
|
const api = {
|
||||||
openNoteWindow: (noteId) => {
|
openNoteWindow: (noteId) => {
|
||||||
ipcRenderer.send('open-note-window', 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) {
|
if (process.contextIsolated) {
|
||||||
try {
|
try {
|
||||||
contextBridge.exposeInMainWorld('electron', electronAPI)
|
|
||||||
contextBridge.exposeInMainWorld('api', api)
|
contextBridge.exposeInMainWorld('api', api)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window.electron = electronAPI
|
|
||||||
window.api = api
|
window.api = api
|
||||||
}
|
}
|
||||||
|
|||||||
48
src/renderer/src/composables/useNotes.js
Normal file
48
src/renderer/src/composables/useNotes.js
Normal file
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="directory layout-block">
|
<main class="directory layout-block">
|
||||||
<button
|
<button class="capitula" v-for="(capitula, i) in capitulum">
|
||||||
class="capitula"
|
|
||||||
v-for="(capitula, i) in capitulum"
|
|
||||||
@click="openNote(capitula.slug)"
|
|
||||||
>
|
|
||||||
<span class="index">{{ String(i + 1).padStart(2, '0') }}.</span>
|
<span class="index">{{ String(i + 1).padStart(2, '0') }}.</span>
|
||||||
<span class="title h1">{{ capitula.title }}</span>
|
<span class="title h1">{{ capitula.title }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -22,38 +22,38 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onBeforeUnmount } from 'vue'
|
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
import { Markdown } from '@tiptap/markdown'
|
import { Markdown } from '@tiptap/markdown'
|
||||||
import Image from '@tiptap/extension-image'
|
import Image from '@tiptap/extension-image'
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
import { Placeholder } from '@tiptap/extensions'
|
import { Placeholder } from '@tiptap/extensions'
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
import { BubbleMenu, FloatingMenu } from '@tiptap/vue-3/menus'
|
import { BubbleMenu } from '@tiptap/vue-3/menus'
|
||||||
// import SvgIconHr from '../svg/icon/Hr.vue'
|
import useNotes from '@/composables/useNotes'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import _debounce from 'lodash/debounce'
|
||||||
|
|
||||||
// Initial markdown string
|
const route = useRoute()
|
||||||
const initialMarkdown = `# My Document
|
const filePath = `/${route.params.id}.md`
|
||||||
|
|
||||||
This is a paragraph.
|
const { readNote, updateNote } = useNotes()
|
||||||
|
|
||||||
# Section
|
|
||||||
|
|
||||||
- Item one
|
|
||||||
- Item two
|
|
||||||
|
|
||||||
---
|
|
||||||
# Header Three
|
|
||||||
|
|
||||||
[Link](https://google.com)
|
|
||||||
`
|
|
||||||
// const initialMarkdown = ``
|
|
||||||
|
|
||||||
const CustomDocument = Document.extend({
|
const CustomDocument = Document.extend({
|
||||||
content: 'heading block*',
|
content: 'heading block*',
|
||||||
})
|
})
|
||||||
|
|
||||||
const editor = new Editor({
|
const editor = shallowRef()
|
||||||
|
|
||||||
|
const updateFile = _debounce(({ editor }) => {
|
||||||
|
const markdown = editor.getMarkdown()
|
||||||
|
updateNote(filePath, markdown)
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const noteFile = await readNote(filePath)
|
||||||
|
|
||||||
|
editor.value = new Editor({
|
||||||
extensions: [
|
extensions: [
|
||||||
CustomDocument,
|
CustomDocument,
|
||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
@@ -73,11 +73,13 @@ const editor = new Editor({
|
|||||||
Markdown,
|
Markdown,
|
||||||
Image,
|
Image,
|
||||||
],
|
],
|
||||||
content: initialMarkdown,
|
content: noteFile,
|
||||||
contentType: 'markdown',
|
contentType: 'markdown',
|
||||||
|
onUpdate: updateFile,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
editor.destroy()
|
editor.value?.destroy?.()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user