From 0a6377c125879214ce5190763e811a8d6f6f75f4 Mon Sep 17 00:00:00 2001 From: Dominic Ricottone Date: Mon, 21 Feb 2022 15:57:34 -0600 Subject: [PATCH] 0.2 rewrite --- index.html | 10 +- main.js | 337 +++++++++++++++++++++++++++++++------- preload.js | 464 +++++++++++++++++++++++++++++++++++----------------- renderer.js | 231 ++++++++++++++------------ 4 files changed, 713 insertions(+), 329 deletions(-) diff --git a/index.html b/index.html index 5f6f845..37a6ec8 100644 --- a/index.html +++ b/index.html @@ -10,19 +10,11 @@
-
+
diff --git a/main.js b/main.js index 5b19153..bd37a5a 100644 --- a/main.js +++ b/main.js @@ -1,48 +1,280 @@ -// to disable logging, comment out the console.log line -function debug(message) { - //console.log("[main] " + message); -} +//////////////////////////// +// Global state goes here // +//////////////////////////// -// constants const { app, BrowserWindow, ipcMain, dialog, shell, Menu } = require("electron"); -const os = require("os"); const path = require("path"); -let win; +const os = require("os"); +var win; + +const dirNotes = path.join(os.homedir(), "notes"); + +const filePreload = path.join(__dirname, "preload.js"); +const fileIndex = path.join(__dirname, "index.html"); + +const urlProject = "https://github.com/dricottone/noticable"; +const urlBugTracker = "https://github.com/dricottone/noticable/issues"; + +// Options for prompt to discard or save changes. +const optionsDiscard = { + message: "You have made changes in the editor. Do you want to discard those changes, or save to a file?", + buttons: ["&Discard", "&Save", "Save &As..."], + type: "question", + normalizeAccessKeys: true, +}; + +// Options for error message that saving failed. +const optionsReSaveAs = { + message: "File could not be saved.", + buttons: ["&Cancel", "&Try Again"], + type: "error", + normalizeAccessKeys: true, +}; + +// Options for prompt to save a file. +const optionsSaveAs = { + title: "Create new note", + defaultPath: dirNotes, + properties: ["showOverwriteConfirmation"], + filters: [ + { name: "Markdown", extensions: ["md"] }, + { name: "Plain Text", extensions: ["txt"] }, + ], +}; + -// configuration -const notesDir = path.join(os.homedir(), "notes"); +/////////////////////// +// Functions go here // +/////////////////////// + +// Push filenames to be relative to the notes directory. +function relativeNotePath(filename) { + return path.relative(dirNotes, filename); +} + +// Prompt to save a file. +function promptSave() { + dialog.showSaveDialog(win, optionsSaveAs) + .then(r => { + if (r.canceled) { + announceFileNotSaved(); + } else { + preloadSaveFile(relativeNotePath(r.filePath)); + } + }); +}; + +// Prompt to save a file after already attempting to do so. +function rePromptSave() { + dialog.showMessageBox(win, optionsSaveError) + .then(r => { + if (r.response==0) { + announceFileNotSaved(); + } else { + dialog.showSaveDialog(win, optionsSaveAs) + .then(r => { + if (r.canceled) { + announceFileNotSaved(); + } else { + preloadReSaveFile(relativeNotePath(r.filePath)); + } + }); + } + }); +}; + +// Prompt to save a file then open a new file. Discard the file if the prompt +// is cancelled. +function promptSaveDiscardableThenNew() { + dialog.showMessageBox(win, optionsDiscard) + .then(r => { + if (r.response==0) { + announceFileDiscardedForNewFile(); + } else if (r.response==1) { + preloadTrySaveFileThenNewFile(); + } else { + dialog.showSaveDialog(win, optionsSaveAs) + .then(r => { + if (r.canceled) { + announceFileDiscardedForNewFile(); + } else { + preloadSaveFileThenNewFile(relativeNotePath(r.filePath)); + } + }); + } + }); +}; + +// Prompt to save a file then read a file. Discard the file if the prompt is +// cancelled. +function promptSaveDiscardableThenRead(filename) { + dialog.showMessageBox(win, optionsDiscard) + .then(r => { + if (r.response==0) { + announceFileDiscardedForReadFile(filename); + } else if (r.response==1) { + preloadTrySaveFileThenReadFile(filename); + } else { + dialog.showSaveDialog(win, optionsSaveAs) + .then(r => { + if (r.canceled) { + announceFileDiscardedForReadFile(filename); + } else { + preloadSaveFileThenReadFile(relativeNotePath(r.filePath), filename); + } + }); + } + }); +}; + +// Prompt to save a file after already attempting to do so. Discard the file if +// the prompt is cancelled. +function rePromptSaveDiscardableThenNew() { + dialog.showMessageBox(win, optionsSaveError) + .then(r => { + if (r.response==0) { + announceFileDiscardedForNewFile(); + } else { + dialog.showSaveDialog(win, optionsSaveAs) + .then(r => { + if (r.canceled) { + announceFileDiscardedForNewFile(); + } else { + preloadReSaveFileThenNewFile(relativeNotePath(r.filePath)); + } + }); + } + }); +}; + +// Ask preload to save a file. +function preloadSaveFile(filename) { + win.webContents.send("saveFile", filename); +}; + +// Ask preload to *try* to save a file. +// NOTE: We don't know if preload has a cached file name. There is no real +// advantage to querying this first. Same number of IPC calls in worst +// case (i.e. no known file name) and excessive calls in best case. +function preloadTrySaveFile() { + win.webContents.send("trySaveFile", ""); +}; + +// Ask preload to save a file. +// NOTE: Triggers different logic. Preload skips querying the renderer for the +// note (because this is a *re*-save). +function preloadReSaveFile(filename) { + win.webContents.send("reSaveFile", filename); +}; + +// Ask preload to save a file then show a new file. +function preloadSaveFileThenNewFile(filename) { + win.webContents.send("saveFileThenNewFile", filename); +}; + +// Ask preload to *try* to save a file then show a new file. +// NOTE: We don't know if preload has a cached file name. There is no real +// advantage to querying this first. Same number of IPC calls in worst +// case (i.e. no known file name) and excessive calls in best case. +function preloadTrySaveFileThenNewFile() { + win.webContents.send("trySaveFileThenNewFile", ""); +}; + +// Ask preload to save a file then show a new file. +// NOTE: Triggers different logic. Preload skips querying the renderer for the +// note (because this is a *re*-save). +function preloadReSaveFileThenNewFile(toSaveFilename, toReadFilename) { + win.webContents.send("reSaveFileThenNewFile", { toSave: toSaveFilename, toRead: toReadFilename }); +}; + +// Ask preload to save a file then read another file. +function preloadSaveFileThenReadFile(toSaveFilename, toReadFilename) { + win.webContents.send("saveFileThenReadFile", { toSave: toSaveFilename, toRead: toReadFilename }); +}; + +// Ask preload to *try* to save a file then read another file. +// NOTE: We don't know if preload has a cached file name. There is no real +// advantage to querying this first. Same number of IPC calls in worst +// case (i.e. no known file name) and excessive calls in best case. +function preloadTrySaveFileThenReadFile(filename) { + win.webContents.send("trySaveFileThenReadFile", filename); +}; + +// Announce that a file was not saved. +function announceFileNotSaved() { + win.webContents.send("fileNotSaved", ""); +}; + +// Announce that a file was not saved and changes should be discarded for a new +// file. +function announceFileDiscardedForNewFile() { + win.webContents.send("fileDiscardedForNewFile", ""); +}; + +// Announce that a file was not saved and changes should be discarded for +// another file to be read. +function announceFileDiscardedForReadFile(filename) { + win.webContents.send("fileDiscardedForReadFile", filename); +}; + +// Ask renderer to sent editor content to be checked against the cached content +// then reset the editor. +function preloadRendererSendContentForCheckThenNew() { + win.webContents.send("rendererSendContentForCheckThenNew", ""); +}; + +// Ask renderer to send content for rendering. +function preloadRendererSendContentForRender() { + win.webContents.send("rendererSendContentForRender", ""); +}; + +// Ask renderer to show the editor. +function preloadRendererShowEditor() { + win.webContents.send("rendererShowEditor", ""); +}; + +// Ask renderer to send content for rendering and show the viewer. +function preloadRendererShowViewer() { + preloadRendererSendContentForRender(); + win.webContents.send("rendererShowViewer", ""); +}; + + +////////////////////////////// +// Electron magic goes here // +////////////////////////////// -// manu const template = [ { label: "File", submenu: [ { - label: "Save and Render", + label: "Save", accelerator: "CmdOrCtrl+S", - click: () => { - win.webContents.send("menu-render-markdown", ""); - } + click: preloadTrySaveFile, }, { - label: "Save", + label: "Save As...", accelerator: "CmdOrCtrl+Shift+S", click: () => { - win.webContents.send("menu-save-text", ""); + promptSave(win); } }, + { + label: "Render Note", + accelerator: "CmdOrCtrl+R", + click: preloadRendererSendContentForRender, + }, { label: "New", accelerator: "CmdOrCtrl+N", - click: () => { - win.webContents.send("menu-new-file", ""); - } + click: preloadRendererSendContentForCheckThenNew, }, { type: "separator" }, { label: "Show Notes Directory", click: async () => { - await shell.openPath(notesDir); + await shell.openPath(dirNotes); } }, { type: "separator" }, @@ -67,24 +299,21 @@ const template = [ label: "View", submenu: [ { - label: "Focus Editor Mode", + label: "Show Editor", accelerator: "CmdOrCtrl+E", - click: () => { - win.webContents.send("menu-focus-editor", ""); - } + click: preloadRendererShowEditor, }, { - label: "Focus Rendered Mode", + label: "Show Viewer", accelerator: "CmdOrCtrl+Shift+E", - click: () => { - win.webContents.send("menu-unfocus-editor", ""); - } + click: preloadRendererShowViewer, }, { type: "separator" }, - { role: "reload" }, - { role: "forceReload" }, - { role: "toggleDevTools" }, - { type: "separator" }, + // NOTE: I believe these should not be enabled in a production build + // { role: "reload" }, + // { role: "forceReload" }, + // { role: "toggleDevTools" }, + // { type: "separator" }, { role: "resetZoom" }, { role: "zoomIn" }, { role: "zoomOut" }, @@ -104,13 +333,13 @@ const template = [ { label: "About", click: async () => { - await shell.openExternal("https://github.com/dricottone/noticable"); + await shell.openExternal(urlProject); } }, { label: "Report Bugs", click: async () => { - await shell.openExternal("https://github.com/dricottone/noticable/issues"); + await shell.openExternal(urlBugTracker); } } ] @@ -118,27 +347,6 @@ const template = [ ]; Menu.setApplicationMenu(Menu.buildFromTemplate(template)); -// messaging -function postFileName(window, event) { - dialog.showSaveDialog(window, { - title: "Create new note", - defaultPath: notesDir, - properties: ["showOverwriteConfirmation"], - filters: [ - { name: "Markdown", extensions: ["md"] }, - { name: "Plain Text", extensions: ["txt"] }, - ] - }) - .then(r => { - if (!r.canceled) { - filename = r.filePath - debug("posting new filename '" + filename + "'..."); - event.sender.send("post-new-filename", filename); - } - }); -} - -// utilities function initializeWindow() { win = new BrowserWindow({ width: 800, @@ -146,23 +354,26 @@ function initializeWindow() { webPreferences: { contextIsolation: true, nodeIntegration: false, - preload: path.join(__dirname, "preload.js") + preload: filePreload, } }); + win.loadFile(fileIndex); - win.loadFile(path.join(__dirname, "index.html")); - - ipcMain.on("request-local-filename", (event) => { - debug("caught request for local filename"); - postFileName(win, event); - }); + //////////////////////////// + // Listen for events here // + //////////////////////////// + ipcMain.on("promptSave", promptSave); + ipcMain.on("promptSaveDiscardableThenNew", promptSaveDiscardableThenNew); + ipcMain.on("promptSaveDiscardableThenRead", (_, filename) => promptSaveDiscardableThenRead(filename)); + ipcMain.on("rePromptSave", rePromptSave); + //ipcMain.on("fileUnreadable", () => {}); + //ipcMain.on("fileUnunwritable", () => {}); win.on("closed", () => { win = null; }); }; -// app event loop app.on("ready", initializeWindow); app.on("window-all-closed", () => { diff --git a/preload.js b/preload.js index 801d84f..6a3014e 100644 --- a/preload.js +++ b/preload.js @@ -1,186 +1,342 @@ -// to disable logging, comment out the console.log line -function debug(message) { - //console.log("[preload] " + message); -} +//////////////////////////// +// Global state goes here // +//////////////////////////// -// constants const { ipcRenderer } = require("electron"); const fs = require("fs"); const os = require("os"); const path = require("path"); const md = require("markdown-it")({ html: true }); -let currentFilename = ""; -let currentFileContent = ""; - -// configuration +var currentFile = ""; +var currentNote = ""; +var currentNotes = []; const newFileButton = "+ New Note"; -const notesDir = path.join(os.homedir(), "notes"); -// messaging -function requestLocalFilename() { - debug("requesting local filename..."); - ipcRenderer.send("request-local-filename", ""); -} -function postFileText(text) { - debug("posting file text..."); - window.postMessage({ type: "post-file-text", text: text }, "*"); -} -function postDisplayFilename(filename) { - debug("posting display file name '" + filename + "'..."); - window.postMessage({ type: "post-display-filename", text: filename }, "*"); -} -function postHTML(html) { - debug("posting HTML..."); - window.postMessage({ type: "post-html", text: html }, "*"); -} -function requestEditorText() { - debug("requesting editor text..."); - window.postMessage({ type: "request-editor-text" }, "*"); -} -function requestFocusEditor() { - debug("requesting focus editor..."); - window.postMessage({ type: "request-focus-editor" }, "*"); -} -function requestUnfocusEditor() { - debug("requesting unfocus editor..."); - window.postMessage({ type: "request-unfocus-editor" }, "*"); -} -function requestToggleEditor() { - debug("requesting toggle editor focus..."); - window.postMessage({ type: "request-toggle-editor" }, "*"); -} -function requestHighlightFilename(filename) { - debug("requesting highlight filename..."); - window.postMessage({ type: "request-highlight-filename", text: filename }, "*"); -} -function requestAlertInvalidFilename() { - debug("requesting alert about invalid filename..."); - window.postMessage({ type: "request-alert-invalid-filename" }, "*"); -} +const dirNotes = path.join(os.homedir(), "notes"); -// utilities -function initNotesList() { - // initialize the new file button - postDisplayFilename(newFileButton); - // initialize the notes files - fs.readdir(notesDir, (err, files) => { - if (err) throw err; - files.forEach(file => { - postDisplayFilename(getPrettyFilename(file)); - }); - }); -} -function getActualFilename(filename) { - unprettyFilename = path.basename(filename, ".md").split(" ").join("_") + ".md"; - return path.join(notesDir, unprettyFilename); -} -function getPrettyFilename(filename) { - return path.basename(filename, ".md").split("_").join(" "); -} -function newFile() { - updateState("",""); - requestLocalFilename(); -} -function readFile(filename) { - if (filename == newFileButton) { - newFile() + +/////////////////////// +// Functions go here // +/////////////////////// + +// Push filenames to be relative to the notes directory. +// NOTE: In this module, we need to concatenate the notes directory and the +// relative path. Different modules have different needs. +function relativeNotePath(filename) { + return path.join(dirNotes, filename); +}; + +// Cache the new file name and ask renderer to send editor content to be saved. +function saveFile(filename) { + currentFile = filename; + rendererSendContentForSave(); +}; + +// If the file name is cached, ask renderer to send editor content to be saved. +// Otherwise ask main to prompt for a new file name and proceed through +// `saveFile` logic. +function trySaveFile() { + if (currentFile == "") { + mainPromptSave(); } else { - let actualFilename = getActualFilename(filename); - fs.readFile(actualFilename, "utf8", (err, content) => { - if (err) requestAlertInvalidFilename(); - updateState(actualFilename,content); - requestHighlightFilename(filename); - }); + rendererSendContentForSave(); } -} -function renderMarkdown(content) { - postHTML(md.render(content)); -} -function updateState(filename, content) { - currentFilename = filename; - currentFileContent = content; - postFileText(content); -} -function saveFile(content) { - if (currentFilename != "") { - currentFileContent = content; - debug("writing file '" + currentFilename + "'"); - fs.writeFile(currentFilename, currentFileContent, (err) => { - if (err) throw err; - }); +}; + +// Cache the new file name and ask renderer to send editor content to be saved +// then reset the editor. +function saveFileThenNewFile(filename) { + currentFile = filename; + rendererSendContentForSaveThenNew(); +}; + +// If the file name is cached, ask renderer to send editor content to be saved. +// Otherwise ask main to prompt for a new file name and proceed through +// `saveFileThenNewFile` logic. +function trySaveFileThenNewFile() { + if (currentFile == "") { + mainPromptSaveDiscardableThenNew(); } else { - debug("no filename; halting write file"); + rendererSendContentForSaveThenNew(); } -} -function saveFileConditional(content) { - if (currentFilename != "") { - if (content != currentFileContent || currentFileContent == "") { - currentFileContent = content; - debug("writing updated file '" + currentFilename + "'"); - fs.writeFile(currentFilename, currentFileContent, (err) => { - if (err) throw err; - }); - return true; +}; + +// If the file name is cached, save the cached content to it. Otherwise ask +// main to prompt for a new file name and proceed through +// `saveFileThenReadFile` logic. +function trySaveFileThenReadFile(filename) { + if (currentFile == "") { + mainPromptSaveDiscardableThenRead(filename); + } else { + writeFileThenReadFile(currentFile, currentNote, filename); + } +}; + +// Read a directory and return a sorted array of all file names. +function readNotesDirectory(directory) { + let files = fs.readdirSync(directory); + return files.sort((a,b) => a.localeCompare(b)); +}; + +// Read a file and send the content and title to the renderer. +function readNoteFromFile(filename) { + fs.readFile(relativeNotePath(filename), "utf8", (err, content) => { + if (err) { + announceFileUnreadable(); } else { - debug("no updates to file; halting write file"); - return false; + currentFile = filename; + currentNote = content; + rendererNewTitle(filename); + rendererNewContent(content); + rendererNewHTML(md.render(content)); } - } else { - debug("no filename; halting write file"); - return false; + }); +}; + +// Reset local cache and update renderer's state. +// NOTE: This is a destructive operation. Changes should have been saved or +// discarded with user permission *first*. +function newNote() { + currentFile = ""; + currentNote = ""; + rendererNewTitle(""); + rendererNewContent(""); + rendererNewHTML(""); +}; + +// Write a note to a file. +function writeFile(filename, content) { + console.log("trying to save " + filename); + fs.writeFile(relativeNotePath(filename), content, (err) => { + if (err) { + announceFileUnwritable(); + mainRePromptSave(); + } else { + currentFile = filename; + currentNote = content; + rendererNewTitle(filename); + } + }); +}; + +// Write a note to a file and then open a new file. +function writeFileThenNewFile(filename, content) { + console.log("trying to save " + filename); + fs.writeFile(relativeNotePath(filename), content, (err) => { + if (err) { + announceFileUnwritable(); + mainRePromptSaveThenNew(); + } else { + rendererAddTitle(filename); + newNote() + } + }); +}; + +// Write a note to a file and then read another file. +function writeFileThenReadFile(toWriteFilename, content, toReadFilename) { + console.log("trying to save " + toWriteFilename); + fs.writeFile(relativeNotePath(toWriteFilename), content, (err) => { + if (err) { + announceFileUnwritable(); + mainRePromptSaveThenRead(); + } else { + rendererAddTitle(toWriteFilename); + readNoteFromFile(toReadFilename); + } + }); +}; + +// Ask renderer to show the editor. +function rendererShowEditor() { + window.postMessage({ type: "showEditor" }, "*") +}; + +// Ask renderer to show the viewer. +function rendererShowViewer() { + window.postMessage({ type: "showViewer" }, "*") +}; + +// Ask renderer to send editor content so that it can be saved. +function rendererSendContentForSave() { + window.postMessage({ type: "sendContentForSave" }, "*") +}; + +// Ask renderer to send editor content so that it can be saved and then +// reset. +function rendererSendContentForSaveThenNew() { + window.postMessage({ type: "sendContentForSaveThenNew" }, "*") +}; + +// Ask renderer to send editor content so that it can be checked and then +// conditionally reset. +function rendererSendContentForCheckThenNew() { + window.postMessage({ type: "sendContentForCheckThenNew" }, "*") +}; + +// Ask renderer to send editor content so that it can be rendered. +function rendererSendContentForRender() { + window.postMessage({ type: "sendContentForRender" }, "*") +}; + +// Ask renderer to add a note title to the sidebar. +function rendererAddTitle(filename) { + if (currentNotes.indexOf(filename)==-1) { + currentNotes.push(filename); + window.postMessage({ type: "addTitle", text: filename }, "*") + } +}; + +// Ask renderer to add a note title to the sidebar. Does not need to trigger a +// re-sort. Should only be used on initialization. +function rendererAddTitleOrdered(filename) { + window.postMessage({ type: "addTitleOrdered", text: filename }, "*") +}; + +// Ask renderer to highlight a new note title in the sidebar. +function rendererNewTitle(filename) { + if (filename!="") { + rendererAddTitle(filename); + window.postMessage({ type: "newTitle", text: filename }, "*") } +}; + +// Ask renderer to show new content in the editor. +function rendererNewContent(content) { + window.postMessage({ type: "newContent", text: content }, "*") +}; + +// Ask renderer to show new HTML in the viewer. +function rendererNewHTML(html) { + window.postMessage({ type: "newHTML", text: html }, "*") +}; + +// Ask main to prompt for a new file name. +function mainPromptSave() { + ipcRenderer.send("promptSave", ""); +}; + +// Ask main to prompt for either permission to discard changes or a new file +// name then open a new file. +function mainPromptSaveDiscardableThenNew() { + ipcRenderer.send("promptSaveDiscardableThenNew", ""); +}; + +// Ask main to prompt for either permission to discard changes or a new file +// name then read a new file. +function mainPromptSaveDiscardableThenRead(filename) { + ipcRenderer.send("promptSaveDiscardableThenRead", filename); +}; + +// Ask main to prompt for a new file after the previous file name failed to +// work. +function mainRePromptSave() { + ipcRenderer.send("rePromptSave", ""); +}; + +// Ask main to prompt for a new file after the previous file name failed to +// write then open a new file. +function mainRePromptSaveThenNew() { + ipcRenderer.send("rePromptSaveThenNew", ""); } -// listen to main process -ipcRenderer.on("post-new-filename", (event, filename) => { - debug("caught file name '" + filename + "'"); - currentFilename = getActualFilename(filename); - prettyFilename = getPrettyFilename(filename); - postDisplayFilename(prettyFilename); - requestHighlightFilename(prettyFilename); -}); -ipcRenderer.on("menu-save-text", () => { - debug("caught menu button for save text"); - requestEditorText(); -}); -ipcRenderer.on("menu-render-markdown", () => { - debug("caught menu button for render markdown"); - requestEditorText(); - requestUnfocusEditor(); -}); -ipcRenderer.on("menu-new-file", () => { - debug("caught menu button for new file"); - requestEditorText(); - newFile(); -}); -ipcRenderer.on("menu-focus-editor", () => { - debug("caught menu button for focus editor"); - requestFocusEditor(); +// Ask main to prompt for a new file after the previous file name failed to +// write then read another file. +function mainRePromptSaveThenRead(filename) { + ipcRenderer.send("rePromptSaveThenRead", filename); +} + +// Announce that a file is unreadable. +function announceFileUnreadable() { + ipcRenderer.send("fileUnreadable", ""); + window.postMessage({ type: "fileUnreadable" }, "*") +}; + +// Announce that a file is unwritable. +function announceFileUnwritable() { + ipcRenderer.send("fileUnwritable", ""); + window.postMessage({ type: "fileUnwritable" }, "*") +}; + +// Main announced that a file was not saved. Broadcast this announcement. +function broadcastFileNotSaved() { + window.postMessage({ type: "fileNotSaved" }, "*") +}; + +// Main announced that a file was discarded. Broadcast this announcement. +function broadcastFileDiscarded() { + window.postMessage({ type: "fileDiscarded" }, "*") +}; + + +//////////////////////////// +// Listen for events here // +//////////////////////////// +ipcRenderer.on("saveFile", (_, filename) => saveFile(filename)); +ipcRenderer.on("saveFileThenNewFile", (_, filename) => saveFileThenNewFile(filename)); +ipcRenderer.on("saveFileThenReadFile", (_, filenames) => writeFileThenReadFile(filenames.toSave, currentNote, filenames.toRead)); +ipcRenderer.on("trySaveFile", () => trySaveFile()); +ipcRenderer.on("trySaveFileThenNewFile", () => trySaveFileThenNewFile()); +ipcRenderer.on("trySaveFileThenReadFile", (_, filename) => trySaveFileThenReadFile(filename)); +ipcRenderer.on("reSaveFile", (_, filename) => writeFile(filename, currentNote)); +ipcRenderer.on("reSaveFileThenNewFile", (_, filename) => writeFileThenNewFile(filename, currentNote)); +ipcRenderer.on("reSaveFileThenReadFile", (_, filenames) => writeFileThenReadFile(filenames.toSave, currentNote, filenames.toRead)); +ipcRenderer.on("fileNotSaved", () => broadcastFileNotSaved()); +ipcRenderer.on("fileDiscardedForNewFile", () => { + broadcastFileDiscarded(); + newNote(); }); -ipcRenderer.on("menu-unfocus-editor", () => { - debug("caught menu button for unfocus editor"); - requestUnfocusEditor(); +ipcRenderer.on("fileDiscardedForReadFile", (_, filename) => { + broadcastFileDiscarded(); + readNoteFromFile(filename); }); +ipcRenderer.on("rendererShowViewer", () => rendererShowViewer()); +ipcRenderer.on("rendererShowEditor", () => rendererShowEditor()); +ipcRenderer.on("rendererSendContentForCheckThenNew", () => rendererSendContentForCheckThenNew()); +ipcRenderer.on("rendererSendContentForRender", () => rendererSendContentForRender()); -// listen to renderer window.addEventListener("message", (event) => { if (event.source != window) return; if (event.data.type) { + let parameter = event.data.text; switch(event.data.type) { - case "post-editor-text": - debug("caught editor text"); - saveFileConditional(event.data.text); - renderMarkdown(event.data.text); + case "contentForSave": + writeFile(currentFile, parameter); + break; + case "contentForSaveThenNew": + writeFileThenNewFile(currentFile, parameter); break; - case "request-file-text": - filename = event.data.text - debug("caught request for text of file '" + filename + "'"); - readFile(filename); + case "contentForSaveThenRead": + writeFileThenReadFile(currentFile, parameter.note, parameter.title); + break; + case "contentForCheckThenNew": + if (currentNote!=parameter) { + currentNote = parameter.note; + mainPromptSaveDiscardableThenNew(); + } else { + newNote(); + } + break; + case "contentForCheckThenRead": + if (currentNote!=parameter.note) { + currentNote = parameter.note; + mainPromptSaveDiscardableThenRead(parameter.title); + } else { + readNoteFromFile(parameter.title) + } + break; + case "contentForRender": + rendererNewHTML(md.render(parameter)); + rendererShowViewer(); break; } } }, false); -// initialize renderer -window.addEventListener("load", initNotesList); +window.addEventListener("load", () => { + currentNotes = readNotesDirectory(dirNotes); + currentNotes.forEach(filename => { + rendererAddTitleOrdered(filename); + }); +}); diff --git a/renderer.js b/renderer.js index 0bac7cf..a5f2162 100644 --- a/renderer.js +++ b/renderer.js @@ -1,9 +1,6 @@ -// to disable logging, comment out the console.log line -function debug(message) { - //console.log("[renderer] " + message); -} - -// initialize monaco editor +//////////////////////////// +// Monaco magic goes here // +//////////////////////////// require.config({ paths: { vs: 'node_modules/monaco-editor/min/vs' } }); require(['vs/editor/editor.main'], function () { window.editor = monaco.editor.create(document.getElementById('container-editor'), { @@ -17,109 +14,137 @@ require(['vs/editor/editor.main'], function () { }); }); -// messaging -function postEditorText() { - debug("posting editor text..."); - window.postMessage({ type: "post-editor-text", text: window.editor.getValue() }, "*"); -} -function requestFileText(filename) { - debug("posting editor text and requesting text of file '" + filename + "'..."); - window.postMessage({ type: "post-editor-text", text: window.editor.getValue() }, "*"); - window.postMessage({ type: "request-file-text", text: filename }, "*"); -} - -// utilities -function buildFilename(filename) { - var el = document.createElement("li"); - el.innerHTML = filename; - el.addEventListener("click", () => { requestFileText(filename); }); - return el -} -function sortFilenames(a, b) { - return ($(b).text()) < ($(a).text()); -} -function focusEditor() { + +/////////////////////// +// Functions go here // +/////////////////////// + +// TODO: Add documentation for all functions. + +// Parse a file name and return a pretty note title. +function cleanTitle(filename) { + return filename.split(".")[0].split("_").join(" "); +}; + +function showEditor() { $("#container-editor").addClass("focused"); - $("#container-rendered").removeClass("focused"); - $("#ui-focus-editor").prop("checked", true); -} -function unfocusEditor() { - if ($("#ui-enable-render").prop("checked")) { - $("#container-editor").removeClass("focused"); - $("#container-rendered").addClass("focused"); - $("#ui-focus-editor").prop("checked", false); - } else { - debug("rendering disabled; halting unfocus editor"); - focusEditor(); - } -} -function toggleEditor() { - if ($("#ui-focus-editor").prop("checked")) { - unfocusEditor(); - postEditorText(); - } else { - focusEditor(); - } -} -function uiToggleEditor() { - //NOTE: Remember that the status accessed here is the 'new' status, i.e. after it had been toggled - if ($("#ui-focus-editor").prop("checked")) { - debug("UI focus editor"); - focusEditor(); - } else { - debug("UI unfocus editor"); - unfocusEditor(); - postEditorText(); - } -} + $("#container-viewer").removeClass("focused"); +}; + +function showViewer() { + $("#container-editor").removeClass("focused"); + $("#container-viewer").addClass("focused"); +}; + +function addTitle(filename) { + let element = document.createElement("li"); + element.innerHTML = cleanTitle(filename); + element.addEventListener("click", () => { preloadContentForCheckThenRead(filename); }); + $("#list-filenames").append(element) +}; + +function newTitle(filename) { + $("#list-filenames li").removeClass("focused") + let selector = "#list-filenames li:contains(" + cleanTitle(filename) + ")" + $(selector).addClass("focused") +}; + +function sortTitles() { + let sidebar = $("#list-filenames") + let titles = sidebar.children("li") + titles.detach().sort((a,b) => a.innerText.localeCompare(b.innerText)) + sidebar.append(titles) +}; + +function newContent(content) { + window.editor.setValue(content); +}; + +function newHTML(html) { + $("#container-viewer").html(html); +}; + +function preloadContentForSave() { + window.postMessage({ type: "contentForSave", text: window.editor.getValue() }, "*"); +}; + +function preloadContentForSaveThenNew() { + window.postMessage({ type: "contentForSaveThenNew", text: window.editor.getValue() }, "*"); +}; + +function preloadContentForSaveThenRead(filename) { + window.postMessage({ type: "contentForSaveThenRead", text: { note: window.editor.getValue(), title: filename } }, "*"); +}; + +function preloadContentForCheckThenNew() { + window.postMessage({ type: "contentForCheckThenNew", text: window.editor.getValue() }, "*"); +}; -// listen to preload +function preloadContentForCheckThenRead(filename) { + window.postMessage({ type: "contentForCheckThenRead", text: { note: window.editor.getValue(), title: filename } }, "*"); +}; + +function preloadContentForRender() { + window.postMessage({ type: "contentForRender", text: window.editor.getValue() }, "*"); +}; + + +//////////////////////////// +// Listen for events here // +//////////////////////////// window.addEventListener("message", (event) => { if (event.source != window) return; if (event.data.type) { + let parameter = event.data.text; switch(event.data.type) { - case "post-file-text": - debug("caught file text"); - window.editor.setValue(event.data.text); - focusEditor(); - break; - case "post-display-filename": - filename = event.data.text - debug("caught display filename '" + filename + "'"); - $("#list-filenames").append(buildFilename(filename)); - $("#list-filenames li").sort(sortFilenames).appendTo("#list-filenames"); - break; - case "post-html": - debug("caught HTML"); - $("#container-rendered").html(event.data.text); - break; - case "request-editor-text": - debug("caught request for editor text"); - postEditorText(); - break; - case "request-focus-editor": - debug("caught request to focus editor"); - focusEditor(); - break; - case "request-unfocus-editor": - debug("caught request to unfocus editor"); - unfocusEditor(); - break; - case "request-toggle-editor": - debug("caught request to toggle editor"); - toggleEditor(); - break; - case "request-highlight-filename": - filename = event.data.text; - debug("caught request to highlight filename '" + filename + "'"); - $("#list-filenames li").removeClass("highlight"); - $("#list-filenames li").each( function(i, li) { - if ( $(li).text()==filename) $(li).addClass("highlight"); - }); - break; - case "request-alert-invalid-filename": - debug("caught request to alert about invalid filename"); - alert("Error: File could not be read"); + case "addTitle": + addTitle(parameter); + sortTitles(); + break; + case "addTitleOrdered": + addTitle(parameter); + break; + case "newTitle": + newTitle(parameter); + break; + case "newContent": + newContent(parameter); + break; + case "newHTML": + newHTML(parameter); + break; + case "sendContentForSave": + preloadContentForSave(); + break; + case "sendContentForSaveThenNew": + preloadContentForSaveThenNew(); + break; + case "sendContentForSaveThenRead": + preloadContentForSaveThenRead(parameter); + break; + case "sendContentForCheckThenNew": + preloadContentForCheckThenNew(); + break; + case "sendContentForRender": + preloadContentForRender(); + break; + case "showEditor": + showEditor(); + break; + case "showViewer": + showViewer(); + break; + case "fileNotSaved": + // TODO: add some UI component to indicate an error occured + break; + case "fileDiscarded": + // TODO: add some UI component to indicate an error occured + break; + case "fileUnreadable": + // TODO: add some UI component to indicate an error occured + break; + case "fileUnwritable": + // TODO: add some UI component to indicate an error occured break; } } -- 2.45.2