M index.html => index.html +1 -9
@@ 10,19 10,11 @@
</head>
<body>
<div class="sidebar">
- <div id="settings">
- <p>Editor Mode:</p>
- <input id="ui-focus-editor" type="checkbox" checked onchange="uiToggleEditor()">
- <label for="ui-focus-editor"></label>
- <p>Enable Rendering:</p>
- <input id="ui-enable-render" type="checkbox" checked>
- <label for="ui-enable-render"></label>
- </div>
<ul id="list-filenames"></ul>
</div>
<div class="container focused" id="container-editor"></div>
<!--NOTE: .markdown-body is the target for Github Markdown CSS module; do not modify!-->
- <div class="container markdown-body" id="container-rendered"></div>
+ <div class="container markdown-body" id="container-viewer"></div>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="node_modules/monaco-editor/min/vs/loader.js"></script>
<script src="renderer.js"></script>
M main.js => main.js +274 -63
@@ 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", () => {
M preload.js => preload.js +310 -154
@@ 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);
+ });
+});
M renderer.js => renderer.js +128 -103
@@ 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;
}
}