M index.html => index.html +10 -1
@@ 10,10 10,19 @@
</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>
- <div class="container markdown-body" id="container-results"></div>
+ <!--NOTE: .markdown-body is the target for Github Markdown CSS module; do not modify!-->
+ <div class="container markdown-body" id="container-rendered"></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 +55 -12
@@ 1,8 1,31 @@
-const { app, BrowserWindow } = require("electron");
+// constants
+const { app, BrowserWindow, ipcMain, dialog } = require("electron");
const shortcut = require("electron-localshortcut");
const path = require("path");
-// initialize renderer
+// configuration
+const notesDir = "notes";
+
+// 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) {
+ console.log("[main] posting new filename...");
+ event.sender.send("post-new-filename", r.filePath);
+ }
+ });
+}
+
+// utilities
let win;
function createWindow() {
win = new BrowserWindow({
@@ 14,30 37,50 @@ function createWindow() {
preload: path.join(__dirname, "preload.js")
}
});
+
win.loadFile(path.join(__dirname, "index.html"));
- shortcut.register(win, "CmdOrCtrl+S", () => {
- console.log("[main] triggering 'request-focus-editor'...");
- win.webContents.send("request-focus-editor", "");
- });
- shortcut.register(win, "CmdOrCtrl+E", () => {
- console.log("[main] triggering 'request-render-markdown'...");
- win.webContents.send("request-render-markdown", "");
+}
+function initializeWindow() {
+ createWindow();
+ registerShortCuts(win);
+
+ ipcMain.on("request-local-filename", (event) => {
+ console.log("[main] caught request for local filename");
+ console.log("[main] sending 'request-focus-editor'...");
+ postFileName(win, event);
});
+
win.on("closed", () => {
win = null;
});
};
-app.on("ready", createWindow);
+function registerShortCuts(window) {
+ shortcut.register(window, "CmdOrCtrl+E", () => {
+ console.log("[main] focus editor");
+ window.webContents.send("key-focus-editor", "");
+ });
+ shortcut.register(window, "CmdOrCtrl+S", () => {
+ console.log("[main] save text and render markdown");
+ window.webContents.send("key-render-markdown", "");
+ });
+ shortcut.register(window, "CmdOrCtrl+Shift+S", () => {
+ console.log("[main] save text");
+ window.webContents.send("key-save-text", "");
+ });
+}
+
+// app event loop
+app.on("ready", initializeWindow);
-// macOS best practices
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
+
app.on("activate", () => {
if (win === null) {
- createWindow();
+ initializeWindow();
}
});
M noticable.css => noticable.css +40 -1
@@ 14,11 14,50 @@ body {
.sidebar li {
color: #fff;
}
+.sidebar p {
+ color: white;
+ display: inline-block;
+}
.sidebar a {
- color: #fff;
+ color: white;
text-decoration: none;
display: block;
}
+#settings input {
+ height: 0;
+ width: 0;
+ visibility: hidden;
+}
+#settings label {
+ top: 0.25em;
+ cursor: pointer;
+ text-indent: -9999px;
+ width: 40px;
+ height: 20px;
+ background: grey;
+ display: inline-block;
+ position: relative;
+}
+#settings label:after {
+ content: "";
+ position: absolute;
+ top: 1px;
+ left: 1px;
+ width: 18px;
+ height: 18px;
+ background: white;
+ transition: 0.5s;
+}
+#settings input:checked + label {
+ background: seagreen;
+}
+#settings input:checked + label:after {
+ left: calc(100% - 1px);
+ transform: translateX(-100%);
+}
+#settings label:active:after {
+ width: 130px;
+}
.container {
max-width: 1000px;
height: 100%;
M package-lock.json => package-lock.json +21 -0
@@ 169,6 169,14 @@
"integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
"optional": true
},
+ "doc-ready": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/doc-ready/-/doc-ready-1.0.4.tgz",
+ "integrity": "sha1-N/U5GWnP+ZQwP9/vLl1QNX+BZNM=",
+ "requires": {
+ "eventie": "^1"
+ }
+ },
"duplexer3": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
@@ 200,6 208,14 @@
"keyboardevents-areequal": "^0.2.1"
}
},
+ "electron-prompt": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/electron-prompt/-/electron-prompt-1.6.0.tgz",
+ "integrity": "sha512-CJaSo7o84JJygBGCHIY1zySn+j2ejsKZZFAmfIrOw8wgriufzl4RchTf9I2HNFvlOiekGj4WAznOaA6gFm5ddQ==",
+ "requires": {
+ "doc-ready": "^1.0.4"
+ }
+ },
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ 236,6 252,11 @@
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"optional": true
},
+ "eventie": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/eventie/-/eventie-1.0.6.tgz",
+ "integrity": "sha1-1P/IsMK15JPCqhsiy+kY067nRDc="
+ },
"extract-zip": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
M package.json => package.json +1 -0
@@ 15,6 15,7 @@
"cash-dom": "^8.1.0",
"electron": "^11.1.0",
"electron-localshortcut": "^3.2.1",
+ "electron-prompt": "^1.6.0",
"github-markdown-css": "^4.0.0",
"jquery": "^3.5.1",
"markdown-it": "^12.0.3",
M preload.js => preload.js +128 -44
@@ 1,78 1,162 @@
+// constants
const { ipcRenderer } = require("electron");
const fs = require("fs")
const path = require("path");
const md = require("markdown-it")();
+let currentFilename = "";
+let currentFileContent = "";
+
+// configuration
+const newFileButton = "+ New Note";
+const notesDir = "notes";
// messaging
-function postFile(text) {
- console.log("[preload] sending 'post-file'...");
- window.postMessage({ type: "post-file", text: text }, "*");
+function requestLocalFilename() {
+ console.log("[preload -> main] requesting local filename...");
+ ipcRenderer.send("request-local-filename", "");
+}
+function postFileText(text) {
+ console.log("[preload -> renderer] posting file text...");
+ window.postMessage({ type: "post-file-text", text: text }, "*");
+}
+function postDisplayFilename(filename) {
+ console.log("[preload -> renderer] posting display file name...");
+ window.postMessage({ type: "post-display-filename", text: filename }, "*");
}
-function requestInsertFilename(filename) {
- console.log("[preload] sending 'request-insert-filename'...");
- window.postMessage({ type: "request-insert-filename", text: filename }, "*");
+function postHTML(html) {
+ console.log("[preload -> renderer] posting HTML...");
+ window.postMessage({ type: "post-html", text: html }, "*");
}
-function requestInsertHTML(html) {
- console.log("[preload] sending 'request-insert-html'...");
- window.postMessage({ type: "request-insert-html", text: html }, "*");
+function requestEditorText() {
+ console.log("[preload -> renderer] requesting editor text...");
+ window.postMessage({ type: "request-editor-text" }, "*");
}
-function requestPostMarkdown() {
- console.log("[preload] sending 'request-post-markdown'...");
- window.postMessage({ type: "request-post-markdown" }, "*");
+function requestFocusEditor() {
+ console.log("[preload -> renderer] requesting focus editor...");
+ window.postMessage({ type: "request-focus-editor" }, "*");
+}
+function requestUnfocusEditor() {
+ console.log("[preload -> renderer] requesting unfocus editor...");
+ window.postMessage({ type: "request-unfocus-editor" }, "*");
}
function requestToggleEditor() {
- console.log("[preload] sending 'request-toggle-editor'...");
+ console.log("[preload -> renderer] requesting toggle editor focus...");
window.postMessage({ type: "request-toggle-editor" }, "*");
}
+function requestAlertInvalidFilename() {
+ console.log("[preload -> renderer] requesting alert about invalid filename...");
+ window.postMessage({ type: "request-alert-invalid-filename" }, "*");
+}
// utilities
-const newFileButton = "+ New Note";
-const notesDir = "notes"
+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 readFile(filename) {
if (filename == newFileButton) {
- postFile("");
+ updateState("","");
+ requestLocalFilename();
} else {
- fs.readFile(path.join(notesDir, filename + ".md"), "utf8", (err, data) => {
- if (err) throw err;
- postFile(data);
+ let actualFilename = getActualFilename(filename);
+ fs.readFile(actualFilename, "utf8", (err, content) => {
+ if (err) requestAlertInvalidFilename();
+ updateState(actualFilename,content);
});
- };
+ }
}
-function renderMarkdown(markdown) {
- requestInsertHTML(md.render(markdown));
+function renderMarkdown(content) {
+ postHTML(md.render(content));
}
-function initNotesList() {
- requestInsertFilename(newFileButton);
-
- fs.readdir(notesDir, (err, files) => {
- if (err) throw err;
- files.forEach(file => {
- requestInsertFilename(file.slice(0,-3))
+function updateState(filename, content) {
+ currentFilename = filename;
+ currentFileContent = content;
+ postFileText(content);
+}
+function saveFile(content) {
+ if (currentFilename != "") {
+ currentFileContent = content;
+ console.log("[preload] writing file '" + currentFilename + "'");
+ fs.writeFile(currentFilename, currentFileContent, (err) => {
+ if (err) throw err;
});
- });
+ } else {
+ console.log("[preload] no filename; cannot write file");
+ }
+}
+function saveFileConditional(content) {
+ if (currentFilename != "") {
+ if (content != currentFileContent) {
+ currentFileContent = content;
+ console.log("[preload] writing updated file '" + currentFilename + "'");
+ fs.writeFile(currentFilename, currentFileContent, (err) => {
+ if (err) throw err;
+ });
+ return true;
+ } else {
+ console.log("[preload] no updates; not writing file");
+ return false;
+ }
+ } else {
+ console.log("[preload] no filename; cannot write file");
+ return false;
+ }
}
// listen to main process
-ipcRenderer.on("request-render-markdown", () => {
- console.log("[preload] caught 'request-render-markdown'!");
- requestPostMarkdown();
+ipcRenderer.on("post-new-filename", (event, filename) => {
+ console.log("[preload] caught file name");
+ currentFilename = getActualFilename(filename);
+ postDisplayFilename(getPrettyFilename(filename));
+});
+ipcRenderer.on("key-save-text", () => {
+ console.log("[preload] caught keybind for save text");
+ requestEditorText();
+});
+ipcRenderer.on("key-render-markdown", () => {
+ console.log("[preload] caught keybind for render markdown");
+ requestEditorText();
+ requestUnfocusEditor();
+});
+ipcRenderer.on("key-focus-editor", () => {
+ console.log("[preload] caught keybind for focus editor");
+ requestFocusEditor();
});
-ipcRenderer.on("request-focus-editor", () => {
- console.log("[preload] caught 'request-focus-editor'!");
- requestToggleEditor();
+ipcRenderer.on("key-unfocus-editor", () => {
+ console.log("[preload] caught keybind for unfocus editor");
+ requestUnfocusEditor();
});
// listen to renderer
window.addEventListener("message", (event) => {
if (event.source != window) return;
- if (event.data.type && (event.data.type == "post-markdown")) {
- console.log("[preload] caught 'post-markdown'!");
- renderMarkdown(event.data.text);
- };
- if (event.data.type && (event.data.type == "request-post-file")) {
- console.log("[preload] caught 'request-post-file'!");
- readFile(event.data.text);
- };
+ if (event.data.type) {
+ switch(event.data.type) {
+ case "post-editor-text":
+ console.log("[preload] caught editor text");
+ saveFileConditional(event.data.text);
+ renderMarkdown(event.data.text);
+ break;
+ case "request-file-text":
+ console.log("[preload] caught request for file text");
+ readFile(event.data.text);
+ break;
+ }
+ }
}, false);
// initialize renderer
M renderer.js => renderer.js +81 -31
@@ 11,50 11,100 @@ require(['vs/editor/editor.main'], function () {
});
// messaging
-function postMarkdown(markdown) {
- console.log("[renderer] sending 'post-markdown'...");
- window.postMessage({ type: "post-markdown", text: markdown }, "*");
+function postEditorText() {
+ console.log("[renderer -> preload] posting editor text...");
+ window.postMessage({ type: "post-editor-text", text: window.editor.getValue() }, "*");
}
-function requestPostFile(filename) {
- console.log("[renderer] triggering 'request-post-file' on '" + filename + "'...");
- window.postMessage({ type: "request-post-file", text: filename }, "*");
+function requestFileText(filename) {
+ console.log("[renderer -> preload] requesting file text...");
+ window.postMessage({ type: "request-file-text", text: filename }, "*");
}
// utilities
-function buildFileName(filename) {
+function buildFilename(filename) {
var el = document.createElement("li");
el.innerHTML = filename;
- el.addEventListener("click", () => { requestPostFile(filename); });
+ el.addEventListener("click", () => { requestFileText(filename); });
return el
}
-function sortFileNames(a, b) {
+function sortFilenames(a, b) {
return ($(b).text()) < ($(a).text());
}
+function focusEditor() {
+ $("#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 {
+ console.log("[renderer] rendering disabled");
+ 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")) {
+ console.log("[renderer] UI focus editor");
+ focusEditor();
+ } else {
+ console.log("[renderer] UI unfocus editor");
+ unfocusEditor();
+ postEditorText();
+ }
+}
// listen to preload
window.addEventListener("message", (event) => {
if (event.source != window) return;
- if (event.data.type && (event.data.type == "request-post-markdown")) {
- console.log("[renderer] caught 'request-post-markdown'!");
- postMarkdown(window.editor.getValue());
- };
- if (event.data.type && (event.data.type == "request-insert-filename")) {
- console.log("[renderer] caught 'request-insert-filename'!");
- $("#list-filenames").append(buildFileName(event.data.text));
- $("#list-filenames li").sort(sortFileNames).appendTo("#list-filenames");
- };
- if (event.data.type && (event.data.type == "request-insert-html")) {
- console.log("[renderer] caught 'request-insert-html'!");
- $("#container-results").html(event.data.text);
- $(".container").toggleClass("focused");
- };
- if (event.data.type && (event.data.type == "request-toggle-editor")) {
- console.log("[renderer] caught 'request-toggle-editor'!");
- $(".container").toggleClass("focused");
- };
- if (event.data.type && (event.data.type == "post-file")) {
- console.log("[renderer] caught 'post-file'!");
- window.editor.setValue(event.data.text);
- };
+ if (event.data.type) {
+ switch(event.data.type) {
+ case "post-file-text":
+ console.log("[renderer] caught file text");
+ window.editor.setValue(event.data.text);
+ focusEditor();
+ break;
+ case "post-display-filename":
+ console.log("[renderer] caught display filename");
+ $("#list-filenames").append(buildFilename(event.data.text));
+ $("#list-filenames li").sort(sortFilenames).appendTo("#list-filenames");
+ break;
+ case "post-html":
+ console.log("[renderer] caught HTML");
+ $("#container-rendered").html(event.data.text);
+ break;
+ case "request-editor-text":
+ console.log("[renderer] caught request for editor text");
+ postEditorText();
+ break;
+ case "request-focus-editor":
+ console.log("[renderer] caught request to focus editor");
+ focusEditor();
+ break;
+ case "request-unfocus-editor":
+ console.log("[renderer] caught request to unfocus editor");
+ unfocusEditor();
+ break;
+ case "request-toggle-editor":
+ console.log("[renderer] caught request to toggle editor");
+ toggleEditor();
+ break;
+ case "request-alert-invalid-filename":
+ console.log("[renderer] caught request to alert about invalid filename");
+ alert("Error: File could not be read");
+ break;
+ }
+ }
}, false);