diff --git a/README.md b/README.md index 1ae0b9f..e201e2a 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,185 @@ # CalendarNotes -An Electron application for daily note (or journal) entries saved to a MySQL DB and searchable by words. +An Electron application for daily notes (or journal) entries saved to a SQLite or MySQL DB and searchable by words. -This is a port of an application originally written in Gambas. Much of the code for the calendar component was taken from a -javascript tutorial taken from here https://code.tutsplus.com/tutorials/how-to-build-a-beautiful-calendar-widget--net-12538 +Current release: +
https://github.com/dhunt84971/CalendarNotes/releases/tag/v1.1.6 -While the settings dialog will create the database tables the setup of the MySQL database and configuration/creation of a -database and username and password is on the end user. Keep in mind, the entered database user must be able to create tables -in order to use the create tables button. -This is a work in progress and JavaScript is relatively new to me, so any suggestions or code improvements are welcome. +This is a port of an application originally written in Gambas. Much of the code for the calendar component came from a +javascript tutorial taken from here: +
https://code.tutsplus.com/tutorials/how-to-build-a-beautiful-calendar-widget--net-12538 -Screenshot(s): + -Application - +## Installation +To install the latest release, download the install package appropriate for the platform (exe for Windows, appimage/deb for Linux). +
+https://github.com/dhunt84971/CalendarNotes/releases - +### Windows Installation +For Windows simply execute the setup.exe file. -Search - +### Linux Installation +There are two options for Linux. - +**App Image Installation** -Settings - +The appimage is probably the easiest to install, but does not typically result in a menu integrated installation meaning it will be necessary to execute the appimage file to launch the application. Simply use a file explorer and open the appimage and the application will run. - +**DEB Installation** + +The deb file is suitable for all debian based distros include Ubuntu, however there is an additional dependency that is not included in the deb file that needs to be installed manually. To install the deb file use the following commands from a terminal in the folder where the package was downloaded: + +``` +sudo apt-get update +sudo apt-get install libappindicator1 +sudo dpkg -i calendarNotes_1.0.1_amd64.deb +``` +NOTE: Substitute the example deb file name with the version that was downloaded. + +This should result in the Calendar Notes application becoming available from the launcher menu. This will depend on the distro being used. + + +## Configuration +When the application is first run it will display a warning that no settings file was found. + + + +After clicking OK the application is ready to use with the default settings. This includes using the default theme and a local SQLite database file. + +### MySQL Configuration +The Calendar Notes application can be configured to store all notes and tasks in a MySQL database. Click on the gear icon in the upper right corner to display the settings and tick the MySQL DB Server radio button. This will display additional settings for making a connection to a MySQL database. + + + +Enter the required information and click on **TEST CONNECTION**. If the connection is successful then provided the user has the correct privileges simply click on the **CREATE TABLES** button. This will create the necessary tables for storing the notes and tasks. If a connection is being made to an existing Calendar Notes database that already has the tables, then skip this step. The main advantage of using a MySQL database is that the notes become accessible from multiple locations. Just install the Calendar Notes application on any number of computers and configure the MySQL connection. Now notes can be accessed from anywhere. + +### Themes +Several pre-configured themes are available from the application settings. Examples of each are displayed below: + +**Default** + + + +**Warm** + + + +**Cool** + + + +**Green** + + + +**Pink** + + + +**Tron** + + + +**Clu** + + + +### Experimental +The **Experimental** option is for enabling features that are still being tested. + +## Features +The main feature of the application is to provide a place to enter and store notes for a particular day on the calendar. Click on a day in the calendar and the notes for that day are displayed. Use the **>>** button to navigate directly to today's notes. + + + +### Tasks +The tasks area provides a single location for any active tasks that need to be always displayed. The tasks are not associated with any particular day and are not archived meaning if changes are made to the tasks area and the **SAVE** button is pressed whatever changes were made to tasks are permanent. That is why this area is meant for reminders, such as TO DO lists. Use the Notes area to record when a task has actually been worked on. + +### Save and Revert +The **SAVE** button is used to save any changes that are made to the notes or tasks. To remind the user to press the **SAVE** button astericks (*) are placed on either side of the **SAVE** button to indicate that an edit has been made but not saved. The only time that edits are automatically saved is when the selected date is changed. **NOTE: This means unsaved edits will be lost if the application is closed.** + +The **REVERT** button is there to remove any unsaved edits. For example, if the user has inadvertantly mashed on the keyboard and destroyed their notes, clicking on the **REVERT** button will pull the unedited notes from the database and load these back into the notes area. This also applies to tasks. + +### Date Navigation +Date navigation is accomplished by clicking on a date in the calendar. This will immediately save the current notes and tasks and load the notes for the selected date. Use the **>>** button to navigate to today and the left and right arrow buttons to move to the previous or next month. By holding the **Ctrl** key and clicking on an arrow it is possible to move 12 months into the past or future. + +### Search +One of the most powerful features of the Calendar Notes application is the simple to use search feature. To search for a note containing some words click on the **SEARCH** button and enter the words into the text field to the left of the **GO** button then click on the **GO** button to execute the search. The search uses an inclusive condition for the words being sought meaning that only notes containing ALL of the words will be included in the results. The results are displayed as buttons below the search entry field. Each button displays a date where matching words were found. + + + +By hovering over a date in the search results a preview of the note will be displayed in the notes area. The words being searched for will be highlighted in the previewed notes. To edit the notes for this day click on the dated button and the calendar will select that date and display the notes. + +### Markdown +A markdown renderer has been included to allow the user to enter notes using markdown syntax. To display the fully rendered markdown click on the **MARKDOWN** button. It is not possible to edit the notes while markdown rendering is enabled. To disable the markdown mode click on the **EDIT** button. The markdown mode applies to the search result previews as well as the currently displayed notes. + + + +### Full Window Notes +In order to keep the application as functional as possible the left side of the application can be hidden so that only the notes editor area is displayed. To do this click on the left pointing arrow next to the word **NOTES** at the top of the notes editor area. To restore the left side, click on the right pointing arrow. + + + + + +## Documents +Currently an experimental feature, enabling documents provides additional functionality allowing the user to create documents and multiple pages for each document. Use the gear to expose the settings and check the box next to Documents to enable the feature. + + + +### Add a Document +To add a document, click on the **DOCS** button to reveal the documents and then click the **+ ADD DOC** button. + + + +A page will be automatically added for the newly created document. The new document and new page will have a default name. Right click on the Document or Page button to display the context menu for the selected item. + + + +Use the document and page context menus to modify the selected item. + +#### Document Context Menu + + **ADD DOC** - Add a document under the selected document. + + + **RENAME** - Rename the selected document. Documents at the same level cannot have the same name. + + + **REMOVE** - Delete the selected document. Deleting a document deletes all sub-documents and pages. + + + **MOVE UP** - Move the selected document up in the list. This does not change the level, only the order. + + + **MOVE DOWN** - Move the selected document down in the list. This does not change the level, only the order. + +#### Page Context Menu + + **RENAME** - Rename the selected page. Pages within the same document cannot have the same name. + + + **REMOVE** - Delete the selected page. + + + **INCREASE INDENT** - Indent the page button to the right. This is just a visual means of establishing a relationship between pages in the same document. + + + **DECREASE INDENT** - Remove indent from the page button. This moves the button to the left by one indent level per click. + + + **MOVE UP** - Move the selected page up in the list. + + + **MOVE DOWN** - Move the selected page down in the list. + +### Drag and Drop +Documents and pages can be moved by dragging and dropping. Dragging a document will move all sub-documents under that document. Pages can be dragged from one document to another. + +## Development Setup +VS Code is the recommended IDE. Install VS Code using the instructions from the VS Code website. Install nodejs and npm: + +For Debian based Linux systems run: +``` +sudo apt-get update +sudo apt-get install nodejs npm +``` + +### Development SQLite Dependencies +To setup a session for development it may be necessary to install python and make sure it is in the path. This is because if a SQLite binary cannot be found for the installed version of Electron it will need to be built from source and the source includes python code. + +For Debian based Linux systems run: +``` +sudo apt-get update +sudo apt-get install python +``` diff --git a/app_documents.js b/app_documents.js new file mode 100644 index 0000000..09f5e57 --- /dev/null +++ b/app_documents.js @@ -0,0 +1,1450 @@ +"use strict"; + +var app_documents = { + + //#region GLOBAL DECLARATIONS + lstDocuments: {}, + dvDocuments: {}, + txtRename: {}, + txtDoc: {}, + contextSelectedDoc: "", + contextSelectedPage: "", + renameTarget: {}, + lastFullPath: "", + indentChange: 10, + draggedPageEl: undefined, + draggedDocEl: undefined, + //#endregion GLOBAL DECLARATIONS + + //#region PAGE RENDER FUNCTIONS + loadPages: function (docFullName) { + return new Promise((resolve, reject) => { + console.log(docFullName); + this.getPages(docFullName) + .then((data) => { + emptyDiv("lstDocs"); + console.log(data); + if(data){ + if (data.length > 0){ + let firstPage = {}; + for (var i = 0; i < data.length; i++) { + let pageBtnEl = addItemtoDiv("lstDocs", data[i].DocName, "btn pageItem", "data-grp=page"); + pageBtnEl.setAttribute("draggable", "true"); + let marginL = data[i].DocIndentLevel * 5; + pageBtnEl.style.marginLeft = `${marginL}px`; + pageBtnEl.addEventListener("click", (e)=>{ + this.btnPage_Clicked(e.target); + }); + pageBtnEl.addEventListener("contextmenu", (e)=>{ + this.btnPage_RtClicked(e); + }); + if (i == 0) firstPage = pageBtnEl; + } + this.selectPage(firstPage); + resolve(); + } + else{ + resolve(); + } + } + else{ + resolve(); + } + }) + .catch((err) => { + console.log(err); + reject(err); + }); + }); + + }, + + docContextMenu: function (el, fullPath) { + this.contextSelectedDoc = fullPath; + var menu = document.querySelector(".docsMenu"); + locateMenu(menu, el.clientX, el.clientY); + }, + + pageContextMenu: function (e) { + let el = e.target; + this.contextSelectedPage = el.innerHTML; + var menu = document.querySelector(".pagesMenu"); + locateMenu(menu, e.clientX, e.clientY); + }, + + showtxtRename: function (el) { + console.log(el); + var rect = el.getBoundingClientRect(); + console.log(rect); + this.txtRename.style.left = rect.left + "px"; + this.txtRename.style.top = rect.top + "px"; + this.txtRename.style.width = rect.width + "px"; + this.txtRename.style.height = rect.height + "px"; + this.txtRename.value = el.innerText; + this.txtRename.classList.remove("hide"); + this.txtRename.focus(); + this.txtRename.select(); + }, + + unselectPageButtons: function () { + let pages = document.querySelectorAll("div[data-grp='page']"); + for (let el of pages){ + el.classList.remove("selected"); + } + }, + + selectPageButton: function (el) { + this.unselectPageButtons(); + el.classList.add("selected"); + }, + + showPageData: function (data) { + this.txtDoc.value = (data) ? data : " "; + showPageMarkdown(); + }, + + //#endregion PAGE RENDER FUNCTIONS + + //#region DATABASE FUNCTIONS + getPagesMySQL: function (docFullName) { + let sql = ` + SELECT DocName, DocIndentLevel + FROM Docs + WHERE DocLocation = '${docFullName}' + ORDER BY PageOrder ASC + `; + return this.execQueryMySQL(sql); + }, + + getPagesSqlite: function (docFullName) { + let sql = ` + SELECT DocName, DocIndentLevel + FROM Docs + WHERE DocLocation = '${docFullName}' + ORDER BY PageOrder ASC + `; + return this.execQuerySqlite(sql); + }, + + addDocLocationMySQL: function (parentDoc, docName, docOrder) { + return new Promise((resolve, reject) => { + var docFullName = parentDoc == "" ? docName : parentDoc + "/" + docName; + // Make sure the document name is unique. + this.getUniqueDocName(docFullName) + .then((docNewName) => { + // Add the document name to the database. + showWaitImage(); + var connection = mysql.createConnection(_settings); + connection.connect(function (err) { + if (err) throw err; + var sql = ` + INSERT INTO Docs + (DocName, DocLocation, DocColor, DocText, LastModified, DocOrder, PageOrder) + VALUES ('New Page', '${docNewName}',-1, '', '${getMySQLNow()}', ${docOrder}, 0) + `; + console.log("Executing SQL query = " + sql); + connection.query(sql, function (err, result) { + hideWaitImage(); + if (err) reject(err) + else resolve(result); + connection.end(); + }); + }); + }); + }); + }, + + addDocLocationSqlite: function (parentDoc, docName, docOrder) { + return new Promise((resolve, reject) => { + var docFullName = parentDoc == "" ? docName : parentDoc + "/" + docName; + // Make sure the document name is unique. + this.getUniqueDocName(docFullName) + .then((docNewName) => { + // Add the document name to the database. + let db = new sqlite3.Database(dbFile, (err) => { + if (err) throw err; + var sql = ` + INSERT INTO Docs + (DocName, DocLocation, DocColor, DocText, LastModified, DocOrder, PageOrder) + VALUES ('New Page', '${docNewName}',-1, '', '${getMySQLNow()}', ${docOrder}, 0) + `; + console.log("Executing SQL query = " + sql); + db.run(sql, (err) => { + if (err) reject(err) + else resolve(); + }); + db.close(); + }); + }); + }); + }, + + addPageMySQL: function (path, docOrder, pageOrder) { + // Get a unique page name. + let newPageName = this.getUniquePageName("New Page"); + // Add the document to the database. + var sql = ` + INSERT INTO Docs + (DocName, DocLocation, DocColor, DocText, LastModified, DocOrder, PageOrder) + VALUES ('${newPageName}', '${path}', + -1, '','${getMySQLNow()}', ${docOrder}, ${pageOrder}) + `; + return this.execCommandMySQL(sql); + }, + + addPageSqlite: function (path, docOrder, pageOrder) { + // Get a unique page name. + let newPageName = this.getUniquePageName("New Page"); + // Add the document to the database. + var sql = ` + INSERT INTO Docs + (DocName, DocLocation, DocColor, DocText, LastModified, DocOrder, PageOrder) + VALUES ('${newPageName}', '${path}', + -1, '','${getMySQLNow()}', ${docOrder}, ${pageOrder}) + `; + return this.execCommandSqlite(sql); + }, + + docNameExistsMySQL: function (docFullName) { + return new Promise(function (resolve, reject) { + var retValue = docFullName; + showWaitImage(); + var connection = mysql.createConnection(_settings); + connection.connect(); + connection.query( + "SELECT DocLocation from Docs WHERE DocLocation = '" + docFullName + "'", + function (err, rows, fields) { + hideWaitImage(); + if (err) { + reject(new Error("DB error occurred!")); + } else { + console.log("Rows found = " + rows.length); + console.log("Returning = " + (rows.length > 0)); + retValue = rows.length > 0; + resolve(retValue); + } + connection.end(); + return retValue; + } + ); + }); + }, + + docNameExistsSqlite: function (docFullName) { + return new Promise(function (resolve, reject) { + var retValue = docFullName; + let db = new sqlite3.Database(dbFile, (err) => { + if (!err) { + let sql = ` + SELECT DocLocation + FROM Docs + WHERE DocLocation = '${docFullName}' + `; + db.all(sql, [], (err, rows) => { + if (!err){ + retValue = rows.length > 0; + resolve(retValue); + } + else{ + reject(err); + } + }); + } + else{ + reject(err); + } + db.close(); + return; + }); + }); + }, + + docPageExistsMySQL: function(fullPath, pageName){ + return new Promise(function (resolve, reject) { + let sql = ` + SELECT DocName + FROM Docs + WHERE DocLocation = '${fullPath}' + AND DocName = '${pageName}'; + `; + app_documents.execQueryMySQL(sql) + .then((data)=>{ + let retValue = data.length > 0; + resolve(retValue); + }) + .catch((err)=>{ + console.log(err); + reject(err); + }); + }); + }, + + docPageExistsSqlite: function(fullPath, pageName){ + return new Promise(function (resolve, reject) { + let sql = ` + SELECT DocName + FROM Docs + WHERE DocLocation = '${fullPath}' + AND DocName = '${pageName}'; + `; + app_documents.execQuerySqlite(sql) + .then((data)=>{ + let retValue = data.length > 0; + resolve(retValue); + }) + .catch((err)=>{ + console.log(err); + reject(err); + }); + }); + }, + + getDocsMySQL: function () { + return new Promise((resolve, reject)=>{ + showWaitImage(); + var connection = mysql.createConnection(_settings); + connection.connect(); + let sql = ` + SELECT DISTINCT DocLocation, DocOrder + FROM Docs + ORDER BY DocOrder ASC + `; + connection.query( sql, (err, data) => { + hideWaitImage(); + if (err) reject(err); + console.log(data); + resolve(data); + connection.end(); + }); + }); + }, + + getDocsSqlite: function () { + // Load treeview with the documents. + let sql = ` + SELECT DISTINCT DocLocation + FROM Docs + ORDER BY DocOrder ASC + `; + return this.execQuerySqlite(sql); + }, + + updateDocNameMySQL: function (oldName, newName){ + var sql1 = ` + UPDATE Docs + SET DocLocation = REPLACE(DocLocation, '${oldName}', '${newName}') + WHERE DocLocation = '${oldName}'; + `; + var sql2= ` + UPDATE Docs + SET DocLocation = CONCAT('${newName}', SUBSTR(DocLocation, LENGTH('${oldName}/'))) + WHERE INSTR(DocLocation, '${oldName}/') = 1; + `; + return Promise.all([this.execCommandMySQL(sql1), this.execCommandMySQL(sql2)]) + .catch((err)=>{ + console.log(err); + ShowWarningMessageBox("Database command failure!"); + }); + }, + + updateDocNameSqlite: function (oldName, newName){ + var sql1 = ` + UPDATE Docs + SET DocLocation = REPLACE(DocLocation, '${oldName}', '${newName}') + WHERE DocLocation = '${oldName}'; + `; + var sql2= ` + UPDATE Docs + SET DocLocation = '${newName}' || SUBSTR(DocLocation, LENGTH('${oldName}/')) + WHERE INSTR(DocLocation, '${oldName}/') = 1; + `; + return Promise.all([this.execCommandSqlite(sql1), this.execCommandSqlite(sql2)]) + .catch((err)=>{ + console.log(err); + ShowWarningMessageBox("Database command failure!"); + }); + }, + + updatePageNameMySQL: function (fullPath, oldName, newName){ + var sql = ` + UPDATE Docs + SET DocName = '${newName}' + WHERE DocLocation = '${fullPath}' + AND DocName = '${oldName}'; + `; + return this.execCommandMySQL(sql); + }, + + updatePageNameSqlite: function (fullPath, oldName, newName){ + var sql = ` + UPDATE Docs + SET DocName = '${newName}' + WHERE DocLocation = '${fullPath}' + AND DocName = '${oldName}'; + `; + return this.execCommandSqlite(sql); + }, + + deleteDocMySQL: function (fullPath){ + var sql1 = ` + DELETE FROM Docs + WHERE DocLocation = '${fullPath}'; + `; + var sql2 = ` + DELETE FROM Docs + WHERE INSTR(DocLocation, '${fullPath}/') = 1; + `; + return Promise.all([this.execCommandMySQL(sql1), this.execCommandMySQL(sql2)]) + .catch((err)=>{ + console.log(err); + ShowWarningMessageBox("Database command failure!"); + }); + }, + + deleteDocSqlite: function (fullPath){ + var sql1 = ` + DELETE FROM Docs + WHERE DocLocation = '${fullPath}'; + `; + var sql2 = ` + DELETE FROM Docs + WHERE INSTR(DocLocation, '${fullPath}/') = 1; + `; + return Promise.all([this.execCommandSqlite(sql1), this.execCommandSqlite(sql2)]) + .catch((err)=>{ + console.log(err); + ShowWarningMessageBox("Database command failure!"); + }); + }, + + deletePageMySQL: function(fullPath, pageName){ + var sql = ` + DELETE FROM Docs + WHERE DocLocation = '${fullPath}' + AND DocName = '${pageName}'; + `; + return this.execCommandMySQL(sql) + .catch((err)=>{ + console.log(err); + ShowWarningMessageBox("Database command failure!"); + }); + }, + + deletePageSqlite: function(fullPath, pageName){ + var sql = ` + DELETE FROM Docs + WHERE DocLocation = '${fullPath}' + AND DocName = '${pageName}'; + `; + return this.execCommandSqlite(sql) + .catch((err)=>{ + console.log(err); + ShowWarningMessageBox("Database command failure!"); + }); + }, + + getPageNoteMySQL: function (fullPath, pageName){ + let sql = ` + SELECT DocText FROM Docs + WHERE DocLocation = '${fullPath}' + AND DocName = '${pageName}'; + `; + return this.execQueryMySQL(sql); + }, + + getPageNoteSqlite: function (fullPath, pageName){ + let sql = ` + SELECT DocText FROM Docs + WHERE DocLocation = '${fullPath}' + AND DocName = '${pageName}'; + `; + return this.execQuerySqlite(sql); + }, + + updatePageMySQL: function(fullPath, pageName, docText){ + let sql = ` + UPDATE Docs + SET DocText = '${sqlSafeText(docText)}', + LastModified = '${getMySQLNow()}' + WHERE DocLocation = '${fullPath}' + AND DocName = '${pageName}'; + `; + return this.execCommandMySQL(sql); + }, + + updatePageSqlite: function(fullPath, pageName, docText){ + let sql = ` + UPDATE Docs + SET DocText = '${sqlSafeText(docText)}', + LastModified = '${getMySQLNow()}' + WHERE DocLocation = '${fullPath}' + AND DocName = '${pageName}'; + `; + return this.execCommandSqlite(sql); + }, + + updatePageIndentMySQL: function(fullPath, pageName, indent){ + let sql = ` + UPDATE Docs + SET DocIndentLevel = ${indent} + WHERE DocLocation = '${fullPath}' + AND DocName = '${pageName}'; + `; + return this.execCommandMySQL(sql); + }, + + updatePageIndentSqlite: function(fullPath, pageName, indent){ + let sql = ` + UPDATE Docs + SET DocIndentLevel = ${indent} + WHERE DocLocation = '${fullPath}' + AND DocName = '${pageName}'; + `; + return this.execCommandSqlite(sql); + }, + + execQueryMySQL: function (sql) { + return new Promise(function (resolve, reject) { + console.log("Executing SQL query = " + sql); + showWaitImage(); + var connection = mysql.createConnection(_settings); + connection.connect(); + connection.query(sql, + function (err, rows, fields) { + hideWaitImage(); + if (err) { + reject(new Error("DB error occurred!")); + } else { + resolve(rows); + } + connection.end(); + } + ); + }); + }, + + execCommandMySQL: function (sql){ + return new Promise((resolve, reject) => { + showWaitImage(); + var connection = mysql.createConnection(_settings); + connection.connect(function (err) { + if (err) reject(err); + console.log("Executing SQL query = " + sql); + connection.query(sql, function (err, result) { + hideWaitImage(); + if (err) reject(err) + else resolve(result); + connection.end(); + }); + }); + }); + }, + + execQuerySqlite: function (sql) { + // Load treeview with the documents. + return new Promise((resolve, reject)=>{ + let db = new sqlite3.Database(dbFile, (err) => { + if (!err) { + db.all(sql, [], (err, rows) => { + if (!err){ + console.log(rows); + resolve(rows); + } + else{ + reject(err); + } + }); + } + else{ + reject(err); + } + db.close(); + return; + }); + }); + }, + + execCommandSqlite: function (sql){ + return new Promise((resolve, reject) => { + // Add the document name to the database. + let db = new sqlite3.Database(dbFile, (err) => { + if (err) throw err; + console.log("Executing SQL command = " + sql); + db.run(sql, (err) => { + if (err) reject(err) + else resolve(); + }); + db.close(); + }); + }); + }, + + //#endregion DATABASE FUNCTIONS + + //#region DATA RETRIEVAL FUNCTIONS + getPages: function (docFullName) { + return (_settings.dbType == "MySql") ? + this.getPagesMySQL(docFullName) : + this.getPagesSqlite(docFullName); + }, + + getPageNote: function (docFullName, pageName){ + return (_settings.dbType == "MySql") ? + this.getPageNoteMySQL(docFullName, pageName) : + this.getPageNoteSqlite(docFullName, pageName); + }, + + getUniqueDocName: function (docFullName) { + return new Promise(async (resolve, reject) => { + var fileNameIndex = 0; + var docReturnName = docFullName; + console.log("looking for name " + docFullName); + var nameFound = await this.docNameExists(docFullName); + console.log("looping"); + while (nameFound) { + console.log("incrementing name"); + fileNameIndex += 1; + docReturnName = docFullName + "(" + fileNameIndex + ")"; + console.log("searching for " + docReturnName); + nameFound = await this.docNameExists(docReturnName); + } + resolve(docReturnName); + return docReturnName; + }); + }, + + docNameExists: function (docFullName) { + return (_settings.dbType == "MySql") ? + this.docNameExistsMySQL(docFullName) : + this.docNameExistsSqlite(docFullName); + }, + + getUniquePageDocName: function (path, pageName) { + return new Promise(async (resolve, reject) => { + var fileNameIndex = 0; + var pageReturnName = pageName; + console.log("looking for name " + pageName); + var nameFound = await this.docPageExists(path, pageName); + console.log("looping"); + while (nameFound) { + console.log("incrementing name"); + fileNameIndex += 1; + pageReturnName = pageName + "(" + fileNameIndex + ")"; + console.log("searching for " + pageReturnName); + nameFound = await this.docPageExists(path, pageReturnName); + } + resolve(pageReturnName); + return pageReturnName; + }); + }, + + docPageExists: function (fullPath, pageName){ + return (_settings.dbType == "MySql") ? + this.docPageExistsMySQL(fullPath, pageName) : + this.docPageExistsSqlite(fullPath, pageName); + }, + + getDocs: function (){ + return (_settings.dbType == "MySql") ? + this.getDocsMySQL() : + this.getDocsSqlite(); + }, + + loadDocs: function (selectFirst) { + return new Promise((resolve, reject)=>{ + document.getElementById("txtDoc").value = ""; + document.getElementById("txtDocView").value = ""; + emptyDiv("lstDocuments"); + emptyDiv("lstDocs"); + this.getDocs() + .then((data)=>{ + console.log(data); + for (var i = 0; i < data.length; i++) { + this.dvDocuments.addTVItem(this.lstDocuments, data[i].DocLocation, false); + } + data.length > 0 ? + document.getElementById("btnAddPage").classList.remove("hide") : + document.getElementById("btnAddPage").classList.add("hide"); + // Pick the first location. + if (selectFirst) this.dvDocuments.selectFirstItem(); + resolve(); + }) + .catch((err)=>{ + console.log(err); + reject(err); + }); + }); + }, + + getMaxDocOrder: function (){ + return new Promise(async (resolve, reject)=>{ + let sql = ` + SELECT MAX(DocOrder) AS MaxDocOrder FROM Docs + `; + let data = _settings.dbType == "MySql" ? + await this.execQueryMySQL(sql) : + await this.execQuerySqlite(sql); + data.length > 0 ? resolve(data[0].MaxDocOrder) : resolve(-1); + }); + }, + + getMaxPageOrder: function (docLocation){ + return new Promise(async (resolve, reject)=>{ + let sql = ` + SELECT MAX(PageOrder) AS MaxPageOrder FROM Docs + WHERE DocLocation = '${docLocation}' + `; + let data = _settings.dbType == "MySql" ? + await this.execQueryMySQL(sql) : + await this.execQuerySqlite(sql); + data.length > 0 ? resolve(data[0].MaxPageOrder) : resolve(-1); + }); + }, + + getDocOrder: function (docLocation){ + return new Promise(async (resolve, reject)=>{ + let sql = ` + SELECT DocOrder FROM Docs + WHERE DocLocation = '${docLocation}' + `; + let data = _settings.dbType == "MySql" ? + await this.execQueryMySQL(sql) : + await this.execQuerySqlite(sql); + data.length > 0 ? resolve(data[0].DocOrder) : resolve(0); + }); + }, + + getUpDocOrder: function (path, docOrder){ + return new Promise(async (resolve, reject)=>{ + let sql = ` + SELECT MAX(DocOrder) AS UpDocOrder FROM Docs + WHERE DocLocation LIKE '${path}%' + AND DocOrder < ${docOrder} + `; + let data = _settings.dbType == "MySql" ? + await this.execQueryMySQL(sql) : + await this.execQuerySqlite(sql); + data.length > 0 ? resolve(data[0].UpDocOrder) : resolve(); + }); + }, + + getDownDocOrder: function (path, docOrder){ + return new Promise(async (resolve, reject)=>{ + let sql = ` + SELECT MIN(DocOrder) AS DownDocOrder FROM Docs + WHERE DocLocation LIKE '${path}%' + AND DocOrder > ${docOrder} + `; + let data = _settings.dbType == "MySql" ? + await this.execQueryMySQL(sql) : + await this.execQuerySqlite(sql); + data.length > 0 ? resolve(data[0].DownDocOrder) : resolve(); + }); + }, + + getPageOrder: function (docLocation, pageName){ + return new Promise(async (resolve, reject)=>{ + let sql = ` + SELECT PageOrder FROM Docs + WHERE DocLocation = '${docLocation}' + AND DocName = '${pageName}' + `; + let data = _settings.dbType == "MySql" ? + await this.execQueryMySQL(sql) : + await this.execQuerySqlite(sql); + resolve(data[0].PageOrder); + }); + }, + + //#endregion DATA RETRIEVAL FUNCTIONS + + //#region DATA TRANSMITTAL FUNCTIONS + addDocLocation: async function (parentDoc, docName) { + let path = this.dvDocuments.getSelectedFullPath(); + let docOrder = await this.getMaxDocOrder(); + docOrder ++; + if (_settings.dbType == "MySql"){ + await this.addDocLocationMySQL(parentDoc, docName, docOrder); + } + else{ + await this.addDocLocationSqlite(parentDoc, docName, docOrder); + } + await this.loadDocs(); + (path) ? this.dvDocuments.setSelectedPath(path) : this.dvDocuments.selectFirstItem(); + }, + + addPage: async function (path) { + let pageOrder = await this.getMaxPageOrder(path); + pageOrder ++; + let docOrder = await this.getDocOrder(path); + return (_settings.dbType == "MySql") ? + this.addPageMySQL(path, docOrder, pageOrder) : + this.addPageSqlite(path, docOrder, pageOrder); + }, + + addPage_Clicked: function(){ + let path = this.dvDocuments.getSelectedFullPath(); + let page = this.getSelectedPageName(); + console.log(path); + this.addPage(path) + .then(()=>{ + this.loadPages(path); + }) + .then(()=>{ + this.selectPageByName(page); + }); + }, + + updatePage: function(path, pageName, docText){ + return (_settings.dbType == "MySql") ? + this.updatePageMySQL(path, pageName, docText) : + this.updatePageSqlite(path, pageName, docText); + }, + + updatePageIndent(path, pageName, indent){ + return (_settings.dbType == "MySql") ? + this.updatePageIndentMySQL(path, pageName, indent) : + this.updatePageIndentSqlite(path, pageName, indent); + }, + + savePage: function(){ + return new Promise((resolve, reject)=>{ + let path = this.lastFullPath; + let pageName = this.getSelectedPageName(); + if (!pageName) resolve(); + let docText = this.txtDoc.value; + this.updatePage(path, pageName, docText) + .then(()=>{ + document.getElementById("btnSave").innerHTML = "SAVE"; + resolve(); + }) + .catch((err)=>{ + console.catch(err); + reject(err); + }); + }); + }, + + getUniqueDocName: function (docFullName) { + return new Promise(async (resolve, reject) => { + var fileNameIndex = 0; + var docReturnName = docFullName; + console.log("looking for name " + docFullName); + var nameFound = await this.docNameExists(docFullName); + console.log("looping"); + while (nameFound) { + console.log("incrementing name"); + fileNameIndex += 1; + docReturnName = docFullName + "(" + fileNameIndex + ")"; + console.log("searching for " + docReturnName); + nameFound = await this.docNameExists(docReturnName); + } + resolve(docReturnName); + return docReturnName; + }); + }, + + getUniquePageName: function (pageName) { + let pages = document.querySelectorAll("div[data-grp='page']"); + let pagesArr = Array.from(pages); + console.log(pagesArr); + let newPageName = pageName; + let idx = 0; + while (pagesArr.find(x=>x.innerText==newPageName)){ + idx += 1; + newPageName = `${pageName} (${idx})`; + } + return newPageName; + }, + + updateDocName: function (oldDocFullPath, newDocFullPath) { + return (_settings.dbType == "MySql") ? + this.updateDocNameMySQL(oldDocFullPath, newDocFullPath) : + this.updateDocNameSqlite(oldDocFullPath, newDocFullPath); + }, + + updatePageName: function (fullPath, oldName, newName) { + return (_settings.dbType == "MySql") ? + this.updatePageNameMySQL(fullPath, oldName, newName) : + this.updatePageNameSqlite(fullPath, oldName, newName); + }, + + deleteDoc: function (fullPath){ + return (_settings.dbType == "MySql") ? + this.deleteDocMySQL(fullPath) : + this.deleteDocSqlite(fullPath); + }, + + deletePage: function (fullPath, pageName){ + return (_settings.dbType == "MySql") ? + this.deletePageMySQL(fullPath, pageName) : + this.deletePageSqlite(fullPath, pageName); + }, + + swapPages: function (fullPath, pageOrder1, pageOrder2){ + return new Promise(async (resolve, reject)=>{ + let holdPageOrder = -999; + let sql = ` + UPDATE Docs SET PageOrder = ${holdPageOrder} + WHERE DocLocation = '${fullPath}' AND PageOrder = ${pageOrder1} + `; + await this.execCommandSql(sql); + sql = ` + UPDATE Docs SET PageOrder = ${pageOrder1} + WHERE DocLocation = '${fullPath}' AND PageOrder = ${pageOrder2} + `; + await this.execCommandSql(sql); + sql = ` + UPDATE Docs SET PageOrder = ${pageOrder2} + WHERE DocLocation = '${fullPath}' AND PageOrder = ${holdPageOrder} + `; + await this.execCommandSql(sql); + resolve(); + }); + }, + + movePageUp: async function (fullPath, pageName){ + let pageOrder1 = await this.getPageOrder(fullPath, pageName); + if (pageOrder1 <= 0) return; // Already at the top. + let pageOrder2 = pageOrder1 - 1; + await this.swapPages(fullPath, pageOrder1, pageOrder2); + this.loadPages(fullPath) + .then(()=>{ + this.selectPageByName(pageName); + }); + }, + + movePageDown: async function (fullPath, pageName){ + let pageOrder1 = await this.getPageOrder(fullPath, pageName); + let maxPageOrder = await this.getMaxPageOrder(fullPath); + if (pageOrder1 == maxPageOrder) return; // Already at the bottom. + let pageOrder2 = pageOrder1 + 1; + await this.swapPages(fullPath, pageOrder1, pageOrder2); + this.loadPages(fullPath) + .then(()=>{ + this.selectPageByName(pageName); + }); + }, + + swapDocs: function (docOrder1, docOrder2){ + return new Promise(async (resolve, reject)=>{ + let holdDocOrder = -999; + let sql = ` + UPDATE Docs SET DocOrder = ${holdDocOrder} + WHERE DocOrder = ${docOrder1} + `; + await this.execCommandSql(sql); + sql = ` + UPDATE Docs SET DocOrder = ${docOrder1} + WHERE DocOrder = ${docOrder2} + `; + await this.execCommandSql(sql); + sql = ` + UPDATE Docs SET DocOrder = ${docOrder2} + WHERE DocOrder = ${holdDocOrder} + `; + await this.execCommandSql(sql); + resolve(); + }); + }, + + moveUpDoc: async function (fullPath){ + // Get the selected location's DocOrder. + let docOrder1 = await this.getDocOrder(fullPath); + if (docOrder1 == 0) return; // Cannot go up from the highest point. + // Get the parent path to the passed fullPath. + let locations = fullPath.split("/"); + let parentPath = locations.length > 1 ? locations.slice(0,locations.length-1).join("/") : ""; + // Get the DocOrder number of the next highest doc location less than this one. + let docOrder2 = await this.getUpDocOrder(parentPath, docOrder1); + if (docOrder2){ + await this.swapDocs(docOrder1, docOrder2); + await this.loadDocs(); + this.dvDocuments.setSelectedPath(fullPath); + } + }, + + moveDownDoc: async function (fullPath){ + // Get the selected location's DocOrder. + let docOrder1 = await this.getDocOrder(fullPath); + // Get the parent path to the passed fullPath. + let locations = fullPath.split("/"); + let parentPath = locations.length > 1 ? locations.slice(0,locations.length-1).join("/") : ""; + // Get the DocOrder number of the next highest doc location less than this one. + let docOrder2 = await this.getDownDocOrder(parentPath, docOrder1); + if (docOrder2){ + await this.swapDocs(docOrder1, docOrder2); + await this.loadDocs(); + this.dvDocuments.setSelectedPath(fullPath); + } + }, + + swapPagesByName: async function (fullPath, pageNameSrc, pageNameDst){ + if (getDocChanged()){ + await this.savePage(); + } + let pageOrder1 = await this.getPageOrder(fullPath, pageNameSrc); + let pageOrder2 = await this.getPageOrder(fullPath, pageNameDst); + await this.swapPages(fullPath, pageOrder1, pageOrder2); + this.loadPages(fullPath) + .then(()=>{ + this.selectPageByName(pageNameSrc); + }); + }, + + movePageByName: async function(fullPath, pageNameSrc, docLocationDst){ + if (getDocChanged()){ + await this.savePage(); + } + let movePageOrder = await this.getMaxPageOrder(docLocationDst); + let newPageName = await this.getUniquePageDocName(docLocationDst, pageNameSrc); + movePageOrder ++; + let sql = ` + UPDATE Docs SET PageOrder = ${movePageOrder}, DocLocation = '${docLocationDst}', + DocName = '${newPageName}' + WHERE DocLocation = '${fullPath}' AND DocName = '${pageNameSrc}' + `; + await this.execCommandSql(sql); + await this.loadDocs(); + this.dvDocuments.setSelectedPath(docLocationDst); + this.loadPages(docLocationDst) + .then(()=>{ + this.selectPageByName(newPageName); + }); + }, + + swapDocsByLocation: async function (docLocationSrc, docLocationDst){ + if (getDocChanged()){ + await this.savePage(); + } + let docOrder1 = await this.getDocOrder(docLocationSrc); + let docOrder2 = await this.getDocOrder(docLocationDst); + await this.swapDocs(docOrder1, docOrder2); + await this.loadDocs(); + this.dvDocuments.setSelectedPath(docLocationDst); + }, + + updateDocLocation: function (docLocation, docNewLocation){ + let sql = ` + UPDATE Docs SET DocLocation = REPLACE(DocLocation, '${docLocation}', '${docNewLocation}') + WHERE DocLocation LIKE '${docLocation}/%' + OR DocLocation = '${docLocation}' + `; + return this.execCommandSql(sql); + }, + + moveDocByLocation: async function (docLocationSrc, docLocationDst){ + // Get the last portion of the source location. + if (getDocChanged()){ + await this.savePage(); + } + let srcLocations = docLocationSrc.split("/"); + let srcLastPath = srcLocations[srcLocations.length-1]; + let srcParentPath = srcLocations.length > 1 ? srcLocations.slice(0,srcLocations.length-1).join("/") : ""; + let dstLocations = docLocationDst.split("/"); + let dstParentPath = dstLocations.length > 1 ? dstLocations.slice(0,dstLocations.length-1).join("/") : ""; + if (srcParentPath == dstParentPath) { // The doc is being dropped onto a doc in the same location. + await this.swapDocsByLocation(docLocationSrc, docLocationDst); + } + else { // The doc is being dropped into a different location. + let docNewLocation = docLocationDst != "" ? `${docLocationDst}/${srcLastPath}`: srcLastPath; + docNewLocation = await this.getUniqueDocName(docNewLocation); + await this.updateDocLocation(docLocationSrc, docNewLocation); + await this.loadDocs(); + this.dvDocuments.setSelectedPath(docNewLocation); + } + }, + + execCommandSql: function (sql){ + return (_settings.dbType == "MySql") ? + this.execCommandMySQL(sql) : + this.execCommandSqlite(sql); + }, + + //#endregion DATA TRANSMITTAL FUNCTIONS + + //#region DOCUMENT MANAGEMENT FUNCTIONS + selectDocument: async function (docName) { + console.log(`Document ${docName} selected.`); + if (getDocChanged()){ + await this.savePage(); + } + this.loadPages(docName); + }, + + renameDoc_Clicked: function (docName) { + let el = this.dvDocuments.getSelectedElement(); + this.showtxtRename(el); + this.renameTarget = "document"; + }, + + renamePage_Clicked: function (fullPath, pageName) { + let el = this.getSelectedPageElement(); + this.showtxtRename(el); + this.renameTarget = "page"; + }, + + renamed: async function (){ + let newName = this.txtRename.value; + if (this.renameTarget == "document"){ + let newFullPath = await this.renameDoc(newName); + await this.loadDocs(); + this.dvDocuments.setSelectedPath(newFullPath); + } + else{ + let fullPath = this.dvDocuments.getSelectedFullPath(); + let oldName = this.getSelectedPageName(); + let newPageName = await this.renamePage(fullPath, oldName, newName); + this.loadPages(this.dvDocuments.getSelectedFullPath()) + .then(()=>{ + this.selectPageByName(newPageName); + }); + } + this.txtRename.classList.add("hide"); + }, + + renameDoc: function(newName){ + return new Promise (async (resolve, reject) => { + // Create the new full path. + let fullPath = this.dvDocuments.getSelectedFullPath(); + let oldName = this.dvDocuments.getSelectedElement().innerText; + let paths = fullPath.split("/"); + paths[paths.length-1] = newName; + let newFullPath = paths.join("/"); + console.log(newFullPath); + // Make sure the new full path does not exist. + + let exists = await this.docNameExists(newFullPath); + if (exists == true){ + ShowWarningMessageBox("Name already exists!"); + reject("Name already exists!"); + } + else{ + await this.updateDocName(fullPath, newFullPath) + .catch((err)=>{reject(err);}); + resolve(newFullPath); + return newFullPath; + } + }); + }, + + renamePage: function (fullPath, oldName, newName){ + return new Promise (async (resolve, reject) => { + let exists = await this.docPageExists(fullPath, newName); + if (exists == true){ + ShowWarningMessageBox("Name already exists!"); + reject("Name already exists!"); + } + else{ + await this.updatePageName(fullPath, oldName, newName) + .catch((err)=>{reject(err);}); + resolve(newName); + return newName; + } + }); + }, + + removeDoc_Clicked: function (docName) { + if (showConfirmationBox("Are you sure?\nNote: Deleting this document deletes all children.")){ + this.deleteDoc(docName) + .then(()=>{ + this.loadDocs(true); + }); + } + }, + + removePage_Clicked: function (pageName) { + if (showConfirmationBox("Are you sure?")){ + let fullPath = this.dvDocuments.getSelectedFullPath(); + this.deletePage(fullPath, pageName) + .then(()=>{ + this.loadPages(this.dvDocuments.getSelectedFullPath()); + }); + } + }, + + pageIncIndent_Clicked: function (pageName) { + let pageEl = this.getSelectedPageElement(); + let marginL = parseInt(pageEl.style.marginLeft); + console.log(marginL); + if (marginL < 30){ + let fullPath = this.dvDocuments.getSelectedFullPath(); + let newMarginL = marginL + this.indentChange; + let indent = newMarginL / this.indentChange; + this.updatePageIndent(fullPath, pageName, indent); + pageEl.style.marginLeft = `${newMarginL}px`; + } + }, + + pageDecIndent_Clicked: function (pageName) { + let pageEl = this.getSelectedPageElement(); + let marginL = parseInt(pageEl.style.marginLeft); + console.log(marginL); + if (marginL >= this.indentChange){ + let fullPath = this.dvDocuments.getSelectedFullPath(); + let newMarginL = marginL - this.indentChange; + let indent = newMarginL / this.indentChange; + this.updatePageIndent(fullPath, pageName, indent); + pageEl.style.marginLeft = `${newMarginL}px`; + } + }, + + pageMoveUp_Clicked: function (pageName) { + let fullPath = this.dvDocuments.getSelectedFullPath(); + this.movePageUp(fullPath, pageName); + }, + + pageMoveDown_Clicked: function (pageName) { + let fullPath = this.dvDocuments.getSelectedFullPath(); + this.movePageDown(fullPath, pageName); + }, + + moveUpDoc_Clicked: function (docName) { + let fullPath = this.dvDocuments.getSelectedFullPath(); + this.moveUpDoc(fullPath, docName); + }, + + moveDownDoc_Clicked: function (docName) { + let fullPath = this.dvDocuments.getSelectedFullPath(); + this.moveDownDoc(fullPath, docName); + }, + + swapPage_Dropped: function (pageNameSrc, pageNameDst) { + let fullPath = this.dvDocuments.getSelectedFullPath(); + this.swapPagesByName(fullPath, pageNameSrc, pageNameDst); + }, + + movePage_Dropped: function (pageNameSrc, docLocationDst) { + let fullPath = this.dvDocuments.getSelectedFullPath(); + this.movePageByName(fullPath, pageNameSrc, docLocationDst); + }, + + moveDoc_Dropped: function (docLocationSrc, docLocationDst) { + this.moveDocByLocation(docLocationSrc, docLocationDst); + }, + + + selectPage: async function(el){ + if (!el) return; + if (getDocChanged()) { + await this.savePage(); + } + this.selectPageButton(el); + let fullPath = this.dvDocuments.getSelectedFullPath(); + let pageName = el.innerHTML; + this.getPageNote(fullPath, pageName) + .then((data)=>{ + if (data){ + this.showPageData(data[0].DocText); + } + this.lastFullPath = fullPath; + }) + .catch((err)=>{ + console.log(err); + }) + }, + + selectPageByName: function(name){ + let pages = document.querySelectorAll("div[data-grp='page']"); + let elSelect = {}; + for (let el of pages){ + if (el.innerHTML == name){ + elSelect = el; + } + } + this.selectPage(elSelect); + }, + + btnPage_Clicked: function (el) { + this.selectPage(el); + }, + + btnPage_RtClicked: function (e) { + this.selectPage(e.target); + this.pageContextMenu(e); + }, + + //#endregion DOCUMENT MANAGEMENT FUNCTIONS + + //#region HELPER FUNCTIONS + getSelectedPageName: function(){ + let el = this.getSelectedPageElement(); + if (el) return el.innerHTML; + return; + }, + + getSelectedPageElement: function(){ + return document.querySelector("#lstDocs .selected"); + }, + //#endregion HELPER FUNCTIONS + + //#region INITIALIZATION + init: function () { + this.lstDocuments = document.getElementById("lstDocuments"); + this.txtRename = document.getElementById("txtRename"); + this.txtDoc = document.getElementById("txtDoc"); + this.dvDocuments = new div_treeview(this.lstDocuments, "/"); + this.dvDocuments.onSelect((text)=> { + this.selectDocument(text); + }); + this.dvDocuments.onRightClick((el, fullPath)=>{ + this.docContextMenu(el, fullPath) + }); + }, + //#endregion INITIALIZATION + +} + +app_documents.init(); + +//#region DOM EVENT HANDLERS +document.getElementById("btnAddDoc").addEventListener("click", () => { + app_documents.addDocLocation("", "New Document"); +}); + +document.getElementById("btnAddPage").addEventListener("click", () => { + app_documents.addPage_Clicked(); +}); + +document.getElementById("txtRename").addEventListener("keyup", (e) =>{ + if (e.key == "Enter"){ + app_documents.renamed(); + } +}); + +document.getElementById("txtRename").addEventListener("click", (e) =>{ + e.stopPropagation(); +}); + +document.getElementById("btnExpandAll").addEventListener("click", (e) =>{ + app_documents.dvDocuments.expandAll(); +}); + +document.getElementById("btnCollapseAll").addEventListener("click", (e) =>{ + app_documents.dvDocuments.collapseAll(); +}); + +//#region DRAG AND DROP EVENT HANDLERS + +document.addEventListener("dragstart", (e)=>{ + if (e.target.classList.contains("pageItem")){ + app_documents.draggedPageEl = e.target; + } + else if (e.target.classList.contains("div_treeview_item")) { + app_documents.draggedDocEl = e.target; + } + e.target.style.opacity = .5; +}); + +document.addEventListener("dragend", (e)=> { + // reset the transparency + e.target.style.opacity = ""; +}); + +document.addEventListener("dragenter", (e)=> { + // highlight potential drop target when the draggable element enters it + if (app_documents.draggedPageEl){ + if (e.target.classList.contains("pageItem")) e.target.classList.add("dragTarget"); + if (e.target.classList.contains("div_treeview_item")) e.target.classList.add("dragTarget"); + } + else if (app_documents.draggedDocEl){ + if (e.target.classList.contains("div_treeview_item")) e.target.classList.add("dragTarget"); + if (e.target.id == "btnAddDoc") e.target.classList.add("dragTarget"); + } +}); + +document.addEventListener("dragleave", (e)=> { + // reset background of potential drop target when the draggable element leaves it + e.target.classList.remove("dragTarget"); +}); + +document.addEventListener("dragover", (e)=> { + // prevent default to allow drop + e.preventDefault(); +}); + +document.addEventListener("drop", (e)=> { + // move dragged elem to the selected drop target + if (app_documents.draggedPageEl){ + if (e.target.classList.contains("pageItem")) { + app_documents.swapPage_Dropped(app_documents.draggedPageEl.innerText, e.target.innerText); + } + else if (e.target.classList.contains("div_treeview_item")){ + console.log(app_documents.dvDocuments.getFullPath(e.target)); + app_documents.movePage_Dropped(app_documents.draggedPageEl.innerText, + app_documents.dvDocuments.getFullPath(e.target)); + } + } + else if (app_documents.draggedDocEl){ + if (e.target.classList.contains("div_treeview_item")){ + app_documents.moveDoc_Dropped(app_documents.dvDocuments.getFullPath(app_documents.draggedDocEl), + app_documents.dvDocuments.getFullPath(e.target)); + } + else if (e.target.id == "btnAddDoc"){ + app_documents.moveDoc_Dropped(app_documents.dvDocuments.getFullPath(app_documents.draggedDocEl), + ""); + } + } + e.target.classList.remove("dragTarget"); + // Reinitialize the dragged item references. + app_documents.draggedDocEl = undefined; + app_documents.draggedPageEl = undefined; + // prevent default action. + e.preventDefault(); +}); + +//#endregion DRAG AND DROP EVENT HANDLERS + +// #region DOC CONTEXT MENU EVENT HANDLERS +document.getElementById("btnAddSubDoc").addEventListener("click", ()=>{ + app_documents.addDocLocation(app_documents.contextSelectedDoc,"New Document"); +}); + +document.getElementById("btnRenameDoc").addEventListener("click", (e)=>{ + app_documents.renameDoc_Clicked(app_documents.contextSelectedDoc); + e.stopPropagation(); + document.querySelector(".docsMenu").classList.add("hide"); +}); + +document.getElementById("btnRemoveDoc").addEventListener("click", (e)=>{ + app_documents.removeDoc_Clicked(app_documents.contextSelectedDoc); +}); + +document.getElementById("btnPageRenameDoc").addEventListener("click", (e)=>{ + app_documents.renamePage_Clicked(app_documents.contextSelectedPage); + e.stopPropagation(); + document.querySelector(".pagesMenu").classList.add("hide"); +}); + +document.getElementById("btnPageRemoveDoc").addEventListener("click", (e)=>{ + app_documents.removePage_Clicked(app_documents.contextSelectedPage); +}); + +document.getElementById("btnPageIncIndent").addEventListener("click", (e)=>{ + app_documents.pageIncIndent_Clicked(app_documents.contextSelectedPage); +}); + +document.getElementById("btnPageDecIndent").addEventListener("click", (e)=>{ + app_documents.pageDecIndent_Clicked(app_documents.contextSelectedPage); +}); + +document.getElementById("btnPageMoveUp").addEventListener("click", (e)=>{ + app_documents.pageMoveUp_Clicked(app_documents.contextSelectedPage); +}); + +document.getElementById("btnPageMoveDown").addEventListener("click", (e)=>{ + app_documents.pageMoveDown_Clicked(app_documents.contextSelectedPage); +}); + +document.getElementById("btnMoveUpDoc").addEventListener("click", (e)=>{ + app_documents.moveUpDoc_Clicked(app_documents.contextSelectedDoc); +}); + +document.getElementById("btnMoveDownDoc").addEventListener("click", (e)=>{ + app_documents.moveDownDoc_Clicked(app_documents.contextSelectedDoc); +}); + +// #endregion DOC CONTEXT MENU EVENT HANDLERS + + +//#endregion DOM EVENT HANDLERS \ No newline at end of file diff --git a/calendar.css b/calendar.css index bc7ebf3..5acc340 100644 --- a/calendar.css +++ b/calendar.css @@ -27,6 +27,10 @@ user-select: none; } +input, button, textarea, :focus { + outline: none; +} + #txtView, #txtView::after, #txtView::before { -webkit-user-select: auto; user-select: auto; @@ -125,6 +129,11 @@ body { width: 100%; } +#btnAddDoc { + justify-content: center; + align-items: center; +} + .vbox { display: flex; flex-direction: column; @@ -188,6 +197,18 @@ body { margin-left: 0px !important; } +.overflowAuto { + overflow: auto; +} + +.overflowHidden { + overflow: hidden; +} + +.overflowScrollY { + overflow-y: scroll; +} + /*#endregion LAYOUT STYLES */ textarea { @@ -199,10 +220,12 @@ textarea { /* #region BUTTONS */ .btn { + /* display:flex; flex-direction: column; justify-content: center; align-items: center; + */ background-color: var(--buttonface); color: var(--buttontext); padding: 5px; @@ -228,6 +251,10 @@ textarea { color: var(--buttonhovertext); } +.btnSmall { + width: 20px; +} + /* #endregion BUTTONS */ /* #region TABS */ @@ -557,6 +584,7 @@ background: var(--buttonhover); box-sizing: border-box; background-color: var(--notesBGColor); color: var(--notesTextColor); + box-shadow: none; } .notes .botButton { @@ -656,6 +684,11 @@ background: var(--buttonhover); color: var(--buttonhovertext); } +.selected { + background-color: var(--buttonhover) !important; + color: var(--buttonhovertext) !important; +} + .tasksButton { min-width: 70px; } @@ -810,6 +843,10 @@ background: var(--buttonhover); right: 5px; } +.version { + font: 10px/1.1 "Helvetica Neue", Helvetica, Arial, san-serif; +} + #settingsSlider { box-sizing: border-box; position: absolute; @@ -826,6 +863,28 @@ background: var(--buttonhover); -webkit-box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.25); } +.boxedValue { + border: 1px; + height: 19px; + width: 160px; + border-style: solid; + border-color: black; + background-color: white; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + padding-left: 5px; + text-overflow: ellipsis; + overflow: hidden; +} + +.browseButton { + height: 19px; + font: 10px/1.1 "Helvetica Neue", Helvetica, Arial, san-serif; + margin-left: 2px; +} + /* #endregion SETTINGS BOX */ /* #region SEARCH RESULT PREVIEW */ @@ -860,6 +919,14 @@ background: var(--buttonhover); margin: 5px 5px 5px 2px; } +.pagesMenu { + background-color: var(--notesMenuBGColor); + position: fixed; + width: 200px; + padding: 5px 5px 5px 5px; + margin: 5px 5px 5px 2px; +} + .btnMenu { background-color: var(--buttonface); color: var(--buttontext); @@ -885,3 +952,68 @@ background: var(--buttonhover); color: var(--buttonhovertext); } /* #endregion CONTEXT MENU */ + +/* #region DOCUMENTS */ + +#txtRename { + position: fixed; + border: 1px; + background-color: var(--notesBGColor); + color: var(--notesTextColor); + border-style: solid; + border-color: lightgray; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + padding-left: 5px; +} + +.pageItem { + background-color: var(--compColor2); + color: #000; + padding: 5px; + text-align: center; + border-style: solid; + border-color: lightgray; + border-width: 1px; + min-height: 30px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.dragTarget { + background-color: var(--buttonhover) !important; +} + +.dropNotAllowed { + cursor: not-allowed; +} +/* #endregion DOCUMENTS */ + +/* #region WAIT IMAGE */ +/* The wait image is displayed in the center of the displaying window.*/ +#imgWaitImage { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width:100px; + height:100px; + -webkit-animation: 2s ease 0s normal forwards 1 fadein; + animation: 2s ease 0s normal forwards 1 fadein; + } + + @keyframes fadein{ + 0% { opacity:0; } + 90% { opacity:0; } + 100% { opacity:1; } + } + + @-webkit-keyframes fadein{ + 0% { opacity:0; } + 90% { opacity:0; } + 100% { opacity:1; } + } +/* #endregion WAIT IMAGE */ \ No newline at end of file diff --git a/calendar.js b/calendar.js index 63186ee..e631ea3 100644 --- a/calendar.js +++ b/calendar.js @@ -1,6 +1,11 @@ -const { dialog } = require("electron").remote; +//#region GLOBAL REFERENCES +const { + dialog +} = require("electron").remote; const electron = require("electron"); -const { remote } = require("electron"); +const { + remote +} = require("electron"); const ipc = require("electron").ipcRenderer; const libAppSettings = require("lib-app-settings"); @@ -8,19 +13,25 @@ var mysql = require("mysql"); var sqlite3 = require("sqlite3").verbose(); var fs = require("fs"); var marked = require("marked"); +//#endregion GLOBAL REFERENCES +//#region GLOBAL VARIABLES var initialLoad = true; var settingsShown = false; var calRows = 5; const settingsFile = "./.settings"; -const dbFile = "./.calendarNotes.db"; +var dbFile = "./.calendarNotes.db"; +var settingsdbFile = "./.calendarNotes.db"; var appSettings = new libAppSettings(settingsFile); var monthDisplayed, daySelected, yearDisplayed; var lastDaySelected; +const APPDIR = electron.remote.app.getAppPath(); +var numWaiting = 0; + // These are placeholders that will be written over when the settings // are read from the settings file. var _settings = { @@ -33,6 +44,7 @@ var _settings = { var calChangeDate; var blockInterface = false; +//#endregion GLOBAL VARIABLES // #region THEMES var select = document.getElementById("selThemes"); @@ -88,18 +100,16 @@ var CALENDAR = function () { label = wrap.find("#label"); wrap.find("#prev").bind("click.calender", function (ev) { - if (ev.ctrlKey){ + if (ev.ctrlKey) { switchYear(false); - } - else{ + } else { switchMonth(false); } }); wrap.find("#next").bind("click.calender", function (ev) { - if (ev.ctrlKey){ + if (ev.ctrlKey) { switchYear(true); - } - else{ + } else { switchMonth(true); } }); @@ -118,35 +128,53 @@ var CALENDAR = function () { // Load the settings from the file. await appSettings.loadSettingsFromFile() - .then((settings)=>{ - document.getElementById("selThemes").selectedIndex = settings.themeIndex; - changeTheme(settings.themeIndex, function () { - initSettingsIcon(); - }); - document.getElementById("txtHost").value = settings.host; - document.getElementById("txtPort").value = settings.port; - document.getElementById("txtDatabase").value = settings.database; - document.getElementById("txtUsername").value = settings.user; - document.getElementById("txtPassword").value = settings.password; - document.getElementById("chkDocuments").checked = settings.documents == true; - settings.documents ? null : document.getElementById("btnDocs").classList.add("hide"); - _settings = settings; - dateSelected(daySelected); - //loadDocs(); - document.getElementById("leftSideBar").style.width = settings.leftSideBarWidth; - document.getElementById("docsSideBar").style.width = settings.docsSideBarWidth; - document.getElementById("optSqlite").checked = (settings.dbType == "Sqlite"); - document.getElementById("optMySql").checked = (settings.dbType == "MySql"); - updateDBSelection("opt" + settings.dbType); - }) - .catch((err)=>{ + .then(async (settings) => { + _settings = settings; + if (!_settings.dbFile) _settings.dbFile = dbFile; + dbFile = _settings.dbFile; + settingsdbFile = dbFile; + console.log(dbFile); + if (!fs.existsSync(dbFile)){ + await createSqliteDB(); + ShowWarningMessageBox("DB file not found. Creating it."); + } + document.getElementById("selThemes").selectedIndex = _settings.themeIndex; + changeTheme(settings.themeIndex, function () { + initSettingsIcon(); + }); + document.getElementById("txtHost").value = _settings.host; + document.getElementById("txtPort").value = _settings.port; + document.getElementById("txtDatabase").value = _settings.database; + document.getElementById("txtUsername").value = _settings.user; + document.getElementById("txtPassword").value = _settings.password; + document.getElementById("chkDocuments").checked = _settings.documents == true; + document.getElementById("chkSpelling").checked = _settings.spellChecking == true; + // Set spell checking. + setSpellChecking(_settings.spellChecking); + if (_settings.documents) { + document.getElementById("btnDocs").classList.remove("hide"); + app_documents.loadDocs(true); + } else { + document.getElementById("btnDocs").classList.add("hide"); + }; + dateSelected(daySelected); + document.getElementById("leftSideBar").style.width = _settings.leftSideBarWidth; + document.getElementById("docsSideBar").style.width = _settings.docsSideBarWidth; + document.getElementById("optSqlite").checked = (_settings.dbType == "Sqlite"); + document.getElementById("optMySql").checked = (_settings.dbType == "MySql"); + updateDBSelection("opt" + _settings.dbType); + document.getElementById("lbldbFile").innerHTML = getFilename(_settings.dbFile); + document.getElementById("lbldbFile").title = _settings.dbFile; + }) + .catch((err) => { // Assume any error means the settings file does not exist and create it. ////alert("No settings found. Configure your settings."); + console.log(err); ShowWarningMessageBox("No settings found. Assigning defaults."); appSettings.setSettingsInFile(getSettingsfromDialog()); createSqliteDB(); - }); - + }); + console.log("1" + document.querySelector(".curr").innerHTML); } @@ -162,7 +190,7 @@ var CALENDAR = function () { year = year || (next ? tempYear + 1 : tempYear - 1); } - + switchMonth(null, months.indexOf(curr[0]), year); } @@ -358,13 +386,15 @@ var CALENDAR = function () { // #endregion CALENDAR OBJECT CODE // #region DATABASE CODE -function getRowsMySql(sql, callback){ +function getRowsMySql(sql, callback) { console.log("SQL = " + sql); + showWaitImage(); var connection = mysql.createConnection(_settings); connection.connect(); connection.query(sql, function (err, rows, fields) { + hideWaitImage(); if (!err) { - if (callback){ + if (callback) { callback(err, rows); } } else { @@ -376,13 +406,13 @@ function getRowsMySql(sql, callback){ connection.end(); } -function getRowsSqlite(sql, callback){ +function getRowsSqlite(sql, callback) { console.log("SQL = " + sql); let db = new sqlite3.Database(dbFile, (err) => { if (!err) { db.all(sql, [], (err, rows) => { if (!err) { - if (callback){ + if (callback) { callback(err, rows); } } else { @@ -396,93 +426,108 @@ function getRowsSqlite(sql, callback){ }); } - -function getNotesMySQL(dateForDay, callback){ +function getNotesMySQL(dateForDay, callback) { + showWaitImage(); var connection = mysql.createConnection(_settings); connection.connect(); - - var sqlQuery = "SELECT * from Notes where NoteDate = '" + dateForDay + "'"; + var sqlQuery = `SELECT * from Notes where NoteDate = '${dateForDay}'`; + console.log(sqlQuery); connection.query(sqlQuery, function (err, rows, fields) { + hideWaitImage(); + console.log("Notes received"); if (!err) { if (rows.length > 0) { - if (callback){ + if (callback) { callback(err, rows[0].NoteText); } } else { - if (callback){ + if (callback) { callback(err, " "); } return; } } else { - if (callback){ callback(err); } + if (callback) { + callback(err); + } return; } connection.end(); }); } -function getNotesSqlite(dateForDay, callback){ +function getNotesSqlite(dateForDay, callback) { + console.log(dbFile); + console.log(dateForDay); + console.log(formatDateSqlite(dateForDay)); + let sql = "SELECT * FROM Notes WHERE NoteDate = '" + formatDateSqlite(dateForDay) + "'"; + console.log(sql); let db = new sqlite3.Database(dbFile, (err) => { if (!err) { - db.all("SELECT * FROM Notes WHERE NoteDate = '" + formatDateSqlite(dateForDay) + "'", [], (err, rows) => { + db.all(sql, [], (err, rows) => { if (!err) { if (rows.length > 0) { - if (callback){ + if (callback) { callback(err, rows[0].NoteText); } } else { - if (callback){ + if (callback) { callback(err, " "); } return; } } else { - if (callback){ callback(err); } + if (callback) { + callback(err); + } return; } db.close(); }); } else { console.error(err.message); - if (callback){ callback(err); } + if (callback) { + callback(err); + } return; } }); } -function saveNotesMySql(dateForDay, notesText) { +function saveNotesMySql(dateForDay, notesText, callback) { //var noteExists = sqlNoteExists(dateForDay); console.log("Saving notes = '" + notesText + "'"); if (notesText == "") notesText = " "; sqlNoteExistsMySql(dateForDay, function (result) { if (result) { - updateNotesMySql(dateForDay, notesText, null); + updateNotesMySql(dateForDay, notesText, callback); } else { - insertNotesMySql(dateForDay, notesText, null); + insertNotesMySql(dateForDay, notesText, callback); } }); } -function saveNotesSqlite(dateForDay, notesText) { +function saveNotesSqlite(dateForDay, notesText, callback) { //var noteExists = sqlNoteExists(dateForDay); console.log("Saving notes = '" + notesText + "'"); if (notesText == "") notesText = " "; sqlNoteExistsSqlite(dateForDay, function (result) { if (result) { - updateNotesSqlite(dateForDay, notesText, null); + updateNotesSqlite(dateForDay, notesText, callback); } else { - insertNotesSqlite(dateForDay, notesText, null); + insertNotesSqlite(dateForDay, notesText, callback); } }); } function getTasksMySql(callback) { + showWaitImage(); var connection = mysql.createConnection(_settings); connection.connect(); - connection.query("SELECT * FROM TasksList LIMIT 1", (err, rows, fields) => { + connection.query("SELECT * FROM TasksList LIMIT 1", (err, rows, fields) => { + hideWaitImage(); if (!err) { console.log(rows); if (callback) { @@ -504,54 +549,78 @@ function getTasksSqlite(callback) { db.all("SELECT * FROM TasksList", [], (err, rows) => { if (!err) { if (rows.length > 0) { - if (callback){ + if (callback) { callback(err, rows[0].TasksList); } } else { - if (callback){ + if (callback) { callback(err, " "); } return; } } else { - if (callback){ callback(err); } + if (callback) { + callback(err); + } return; } db.close(); }); } else { console.error(err.message); - if (callback){ callback(err); } + if (callback) { + callback(err); + } return; } }); } -function createSqliteDB(callback){ +function createSqliteDB(callback) { return new Promise((resolve, reject) => { let db = new sqlite3.Database(dbFile, (err) => { if (err) { console.error(err.message); reject(); } else { - // Create the tables. - var sql = `CREATE TABLE Notes ( - ID INTEGER PRIMARY KEY, - NoteDate TEXT, - NoteText TEXT, - LastModified TEXT - )`; - db.run(sql); - sql = `CREATE TABLE TasksList ( - ID INTEGER PRIMARY KEY, - TasksList TEXT - )`; - db.run(sql); - db.close(); - resolve(); - if (callback) callback(); + (async (err) => { + // Create the tables. + var sql = `CREATE TABLE Notes ( + ID INTEGER PRIMARY KEY, + NoteDate TEXT, + NoteText TEXT, + LastModified TEXT + )`; + await new Promise((resolve)=>{ + db.run(sql,()=>{ resolve(); }); + }); + sql = `CREATE TABLE TasksList ( + ID INTEGER PRIMARY KEY, + TasksList TEXT + )`; + await new Promise((resolve)=>{ + db.run(sql,()=>{ resolve(); }); + }); + sql = `CREATE TABLE Docs ( + ID INTEGER PRIMARY KEY, + DocName TEXT, + DocLocation TEXT, + DocColor INTEGER DEFAULT 16777215, + DocText TEXT, + LastModified TEXT, + DocIndentLevel INTEGER DEFAULT 0, + DocOrder INTEGER DEFAULT 0, + PageOrder INTEGER DEFAULT 0 + )`; + await new Promise((resolve)=>{ + db.run(sql,()=>{ resolve(); }); + }); + db.close(()=>{ + resolve(); + if (callback) callback(); + }); + })(); } - console.log('Connected to the database.'); }); }); } @@ -559,7 +628,7 @@ function createSqliteDB(callback){ // #region NOTES CODE // Get the notes from the MySQL database. -function loadNotes(notes){ +function loadNotes(notes) { document.getElementById("txtNotes").value = notes; if (!document.getElementById("btnViewText").classList.contains("btnSelected")) { document.getElementById("txtView").innerHTML = marked(notes); @@ -567,9 +636,9 @@ function loadNotes(notes){ } function getNotes(dateForDay, callback) { - return new Promise(function(resolve, reject){ + return new Promise(function (resolve, reject) { // Block the interface from acting on any input. - if (_settings.dbType == "MySql"){ + if (_settings.dbType == "MySql") { getNotesMySQL(dateForDay, (err, notes) => { if (!err) { loadNotes(notes); @@ -598,19 +667,24 @@ function getNotes(dateForDay, callback) { } function saveNotes(dateForDay, notesText) { - //var noteExists = sqlNoteExists(dateForDay); - console.log("Saving notes = '" + notesText + "'"); - if (notesText == "") notesText = " "; - if (_settings.dbType == "MySql"){ - saveNotesMySql(dateForDay, notesText); - } - else { - saveNotesSqlite(dateForDay, notesText); - } - document.getElementById("btnSave").innerHTML = "SAVE"; + return new Promise(function (resolve, reject) { + console.log("Saving notes = '" + notesText + "'"); + if (notesText == "") notesText = " "; + if (_settings.dbType == "MySql") { + saveNotesMySql(dateForDay, notesText, () => { + resolve(); + }); + } else { + saveNotesSqlite(dateForDay, notesText, () => { + resolve(); + }); + } + document.getElementById("btnSave").innerHTML = "SAVE"; + }); } function updateNotesMySql(dateForDay, notesText, callback) { + showWaitImage(); var connection = mysql.createConnection(_settings); connection.connect(function (err) { if (err) throw err; @@ -620,6 +694,7 @@ function updateNotesMySql(dateForDay, notesText, callback) { console.log("Executing SQL query = " + sql); connection.query(sql, function (err, result) { + hideWaitImage(); if (err) throw err; console.log(result.affectedRows + " record(s) updated"); if (callback) callback(err, result); @@ -646,6 +721,7 @@ function updateNotesSqlite(dateForDay, notesText, callback) { } function insertNotesMySql(dateForDay, notesText, callback) { + showWaitImage(); var connection = mysql.createConnection(_settings); connection.connect(function (err) { if (err) throw err; @@ -656,6 +732,7 @@ function insertNotesMySql(dateForDay, notesText, callback) { console.log("Executing SQL query = " + sql); connection.query(sql, function (err, result) { + hideWaitImage(); if (err) throw err; if (callback) callback(err, result); connection.end(); @@ -674,7 +751,7 @@ function insertNotesSqlite(dateForDay, notesText, callback) { db.run(sql, (err) => { if (err) throw err; - if (callback) callback(err, result); + if (callback) callback(err, "success"); }); db.close(); }); @@ -682,6 +759,7 @@ function insertNotesSqlite(dateForDay, notesText, callback) { function sqlNoteExistsMySql(dateForDay, callback) { var retValue = false; + showWaitImage(); var connection = mysql.createConnection(_settings); connection.connect(); console.log( @@ -694,6 +772,7 @@ function sqlNoteExistsMySql(dateForDay, callback) { connection.query( "SELECT * from Notes where NoteDate = '" + dateForDay + "'", function (err, rows, fields) { + hideWaitImage(); if (!err) { console.log("Rows found = " + rows.length); console.log("Returning = " + (rows.length > 0)); @@ -732,24 +811,24 @@ function sqlNoteExistsSqlite(dateForDay, callback) { return retValue; } -function saveTasksMySql(tasksText) { +function saveTasksMySql(tasksText, callback) { //var noteExists = sqlNoteExists(dateForDay); sqlTasksExistsMySql(function (result) { if (result) { - updateTasksMySql(tasksText, null); + updateTasksMySql(tasksText, callback); } else { - insertTasksMySql(tasksText, null); + insertTasksMySql(tasksText, callback); } }); } -function saveTasksSqlite(tasksText) { +function saveTasksSqlite(tasksText, callback) { //var noteExists = sqlNoteExists(dateForDay); sqlTasksExistsSqlite(function (result) { if (result) { - updateTasksSqlite(tasksText, null); + updateTasksSqlite(tasksText, callback); } else { - insertTasksSqlite(tasksText, null); + insertTasksSqlite(tasksText, callback); } }); } @@ -761,21 +840,50 @@ function showNoteMarkdown() { console.log("show notesText = " + notesText.value); var customMods = notesText.value; console.log("Loading markdown view."); - + // Replace all check marks with their respective images. - // ~_~ = - // ~X~ = - customMods = customMods.replace(/\|_\|/g, "") - customMods = customMods.replace(/\|X\|/g, "") + // |_| = + // |X| = + var checkedSrc = ""; + var uncheckedSrc = ""; + if (_settings.themeIndex == 5) { + checkedSrc = ""; + uncheckedSrc = "" + } else if (_settings.themeIndex == 6) { + checkedSrc = ""; + uncheckedSrc = "" + } + customMods = customMods.replace(/\|X\|/g, checkedSrc); + customMods = customMods.replace(/\|_\|/g, uncheckedSrc); var markedNote = marked(customMods); viewDiv.innerHTML = markedNote; - //marked(notesText.value, () => { - //viewDiv.classList.remove("hide"); - //}); +} +function showPageMarkdown() { + var viewDiv = document.getElementById("txtDocView"); + var notesText = document.getElementById("txtDoc"); + var customMods = notesText.value; + // Replace all check marks with their respective images. + // |_| = + // |X| = + var checkedSrc = ""; + var uncheckedSrc = ""; + if (_settings.themeIndex == 5) { + checkedSrc = ""; + uncheckedSrc = "" + } else if (_settings.themeIndex == 6) { + checkedSrc = ""; + uncheckedSrc = "" + } + customMods = customMods.replace(/\|X\|/g, checkedSrc); + customMods = customMods.replace(/\|_\|/g, uncheckedSrc); + + var markedNote = marked(customMods); + + viewDiv.innerHTML = markedNote; } function hideAllViews() { @@ -791,11 +899,12 @@ function notesViewSelected() { document.getElementById("btnViewText").classList.add("btnSelected"); document.getElementById("btnViewMD").classList.remove("btnSelected"); if (document.getElementById("btnDocs").classList.contains("tabSelected")) { + document.getElementById("divDocsView").classList.remove("hide"); document.getElementById("txtDocArea").classList.remove("hide"); + document.getElementById("txtDocView").classList.add("hide"); } else { document.getElementById("txtNotesArea").classList.remove("hide"); } - } function mdViewSelected() { @@ -803,11 +912,14 @@ function mdViewSelected() { document.getElementById("btnViewText").classList.remove("btnSelected"); document.getElementById("btnViewMD").classList.add("btnSelected"); if (document.getElementById("btnDocs").classList.contains("tabSelected")) { + document.getElementById("divDocsView").classList.remove("hide"); document.getElementById("txtDocView").classList.remove("hide"); + document.getElementById("txtDocArea").classList.add("hide"); } else { document.getElementById("txtView").classList.remove("hide"); } showNoteMarkdown(); + showPageMarkdown(); } function docsViewSelected() { @@ -834,15 +946,15 @@ function docsViewUnselected() { // #endregion NOTES CODE // #region TASKS CODE -function loadTasks(tasks){ +function loadTasks(tasks) { document.getElementById("txtTasks").value = tasks; } // Get the tasks from the database. function getTasks() { - return new Promise(function(resolve, reject){ - // Block the interface from acting on any input. - if (_settings.dbType == "MySql"){ + return new Promise(function (resolve, reject) { + // Block the interface from acting on any input. + if (_settings.dbType == "MySql") { getTasksMySql((err, tasks) => { if (!err) { loadTasks(tasks); @@ -871,24 +983,30 @@ function getTasks() { } function saveTasks(tasksText) { - //var noteExists = sqlNoteExists(dateForDay); - if (_settings.dbType == "MySql"){ - saveTasksMySql(tasksText); - } - else { - saveTasksSqlite(tasksText); - } + return new Promise(function (resolve, reject) { + if (_settings.dbType == "MySql") { + saveTasksMySql(tasksText, () => { + resolve(); + }); + } else { + saveTasksSqlite(tasksText, () => { + resolve(); + }); + } + }); } function updateTasksMySql(tasksText, callback) { + showWaitImage(); var connection = mysql.createConnection(_settings); connection.connect(function (err) { if (err) throw err; var sql = "UPDATE TasksList SET TasksList = '" + sqlSafeText(tasksText) + "'"; console.log("Executing SQL query = " + sql); - connection.query(sql, function (err, result) { + hideWaitImage(); + console.log("4"); if (err) throw err; console.log(result.affectedRows + " record(s) updated"); if (callback) callback(err, result); @@ -913,6 +1031,7 @@ function updateTasksSqlite(tasksText, callback) { } function insertTasksMySql(tasksText, callback) { + showWaitImage(); var connection = mysql.createConnection(_settings); connection.connect(function (err) { if (err) throw err; @@ -921,6 +1040,7 @@ function insertTasksMySql(tasksText, callback) { console.log("Executing SQL query = " + sql); connection.query(sql, function (err, result) { + hideWaitImage(); if (err) throw err; if (callback) callback(err, result); connection.end(); @@ -947,11 +1067,13 @@ function insertTasksSqlite(tasksText, callback) { function sqlTasksExistsMySql(callback) { var retValue = false; + showWaitImage(); var connection = mysql.createConnection(_settings); connection.connect(); console.log("Searching for Tasks : " + "SELECT * from TasksList"); connection.query("SELECT * from TasksList", function (err, rows, fields) { + hideWaitImage(); if (!err) { console.log("Rows found = " + rows.length); console.log("Returning = " + (rows.length > 0)); @@ -986,255 +1108,13 @@ function sqlTasksExistsSqlite(callback) { // #endregion TASKS CODE -// #region DOCS CODE - -var lstDocuments = document.getElementById("lstDocuments"); -var dvDocuments = new div_treeview(lstDocuments, "/"); -var documentSelected = function (text){ - selectDocument(text); -} -dvDocuments.onSelect(documentSelected); -var onDocContextMenu = function (el, fullPath){ - docContextMenu(el, fullPath); -} -dvDocuments.onRightClick(onDocContextMenu); - -// :: TODO :: - -// Show Doc context menu -var contextSelectedDoc; -function docContextMenu (el, fullPath){ - contextSelectedDoc = fullPath; - console.log(el.clientX); - var menu = document.querySelector(".docsMenu"); - menu.style.left = el.clientX + "px"; - menu.style.top = el.clientY + "px"; - menu.classList.remove("hide"); - console.log(menu); -} - -// Select doc -function selectDocument(docName){ - loadPages(docName); -} - -// Load docs -function loadDocs(){ - // Load treeview with the documents. - var connection = mysql.createConnection(_settings); - connection.connect(); - connection.query( - "SELECT DISTINCT DocLocation from Docs", function (err, data) { - if (err) throw err; - console.log(data); - for(var i=0; i { - win.webContents.send('data', oldName); - }); - - win.once('ready-to-show', () => { - win.show(); - }); - -}; - - -// Remove doc -function removeDoc(docFullPath){ - // Display warning confirmation dialog to remove doc. -} - -// Update docs - -// Add doc -function addDocLocation(parentDoc, docName, callback){ - var docFullName = parentDoc == "" ? docName : parentDoc + "/" + docName; - // Make sure the document name is unique. - var docNewName; - getUniqueDocName(docFullName, (docNewName)=>{ - // Add the document name to the database. - var connection = mysql.createConnection(_settings); - connection.connect(function (err) { - if (err) throw err; - var sql = "INSERT INTO Docs (DocName, DocLocation, DocColor, DocText, LastModified) VALUES ("; - sql += "'New Page', "; - sql += "'" + docNewName + "', "; - sql += "-1, "; - sql += "'', "; - sql += "'" + getMySQLNow() + "')"; - console.log("Executing SQL query = " + sql); - - connection.query(sql, function (err, result) { - if (err) throw err; - if (callback) callback(err, result); - connection.end(); - }); - dvDocuments.addTVItem(lstDocuments, docNewName, false); - selectDocument(docNewName); - }); - }); -} - -function docNameExists(docFullName, callback){ - return new Promise(function(resolve, reject){ - var retValue = docFullName; - var connection = mysql.createConnection(_settings); - connection.connect(); - connection.query( - "SELECT DocLocation from Docs WHERE DocLocation = '" + docFullName + "'", - function (err, rows, fields) { - if (err){ - reject(new Error("DB error occurred!")); - } - else{ - console.log("Rows found = " + rows.length); - console.log("Returning = " + (rows.length > 0)); - retValue = rows.length > 0; - if (callback) callback(retValue); - resolve(retValue); - } - connection.end(); - } - ); - }); -} - -async function getUniqueDocName(docFullName, callback){ - var fileNameIndex = 0; - var docReturnName = docFullName; - console.log("looking for name " + docFullName); - var nameFound = await docNameExists(docFullName); - console.log("looping"); - while (nameFound){ - console.log("incrementing name"); - fileNameIndex += 1; - docReturnName = docFullName + "(" + fileNameIndex + ")"; - console.log("searching for " + docReturnName); - nameFound = await docNameExists(docReturnName); - } - if (callback){ - callback(docReturnName); - } - return docReturnName; -} - -// Load pages -function emptyDiv(divById){ - document.getElementById(divById).innerHTML = ""; -} - -function addItemtoDiv(divById, itemInnerText, classAdd){ - var newItem = document.createElement("div"); - newItem.innerText = itemInnerText; - var classes = classAdd.split(" "); - for (var i=0; i 2 ? parts[0] : parts[2]; + let month = parts[0].length > 2 ? parts[1].padStart(2, "0") : parts[0].padStart(2, "0"); + let day = parts[0].length > 2 ? parts[2].padStart(2, "0") : parts[1].padStart(2, "0"); return [year, month, day].join('-'); } @@ -1289,15 +1169,19 @@ function getSelectedDate() { } /// Create SQL table. -function createSQLTable(_settings, query, callback) { - var connection = mysql.createConnection(_settings); - connection.connect(function (err) { - if (err) throw err; - console.log("Executing SQL query = " + query); - - connection.query(query, function (err, result) { - if (err) throw err; - if (callback) callback(err); +function execSqlQuery(_settings, query, callback) { + return new Promise ((resolve, reject)=>{ + showWaitImage(); + var connection = mysql.createConnection(_settings); + connection.connect(function (err) { + if (err) reject(err); + console.log("Executing SQL query = " + query); + connection.query(query, function (err, result) { + hideWaitImage(); + if (err) reject(err); + resolve(result); + if (callback) callback(result); + }); }); }); } @@ -1324,7 +1208,7 @@ function searchNotes(srchText, callback) { } } - var processRows = function (err, rows){ + var processRows = function (err, rows) { if (!err) { if (rows.length > 0) { console.log("Search results found = " + rows.length); @@ -1342,17 +1226,17 @@ function searchNotes(srchText, callback) { } } - if (_settings.dbType == "MySql"){ + if (_settings.dbType == "MySql") { var sql = - "SELECT DATE_FORMAT(NoteDate, '%m/%d/%Y') as srchDate FROM Notes " + - where + - " ORDER BY NoteDate DESC"; + "SELECT DATE_FORMAT(NoteDate, '%m/%d/%Y') as srchDate FROM Notes " + + where + + " ORDER BY NoteDate DESC"; getRowsMySql(sql, processRows); } else { - var sql = - "SELECT NoteDate, strftime('%m/%d/%Y', NoteDate) as srchDate FROM Notes " + - where + - " ORDER BY NoteDate DESC"; + var sql = + "SELECT NoteDate, strftime('%m/%d/%Y', NoteDate) as srchDate FROM Notes " + + where + + " ORDER BY NoteDate DESC"; getRowsSqlite(sql, processRows); } if (callback) callback(); @@ -1435,7 +1319,7 @@ function highlightWords(words, content, markD) { "

" ); // Get rid of ther last
. - if (newContent.substring(newContent.length - 5, newContent.length) == "
"){ + if (newContent.substring(newContent.length - 5, newContent.length) == "
") { newContent = newContent.slice(0, -5); } } @@ -1469,7 +1353,7 @@ function getNotePreview(dateForDay, callback) { console.log(_settings); } } - + var sql = "SELECT * from Notes where NoteDate = '" + convertMySQLDate(dateForDay) + @@ -1479,10 +1363,9 @@ function getNotePreview(dateForDay, callback) { formatDateSqlite(dateForDay) + "'"; console.log(sql); - if (_settings.dbType == "MySql"){ + if (_settings.dbType == "MySql") { getRowsMySql(sql, processRows); - } - else { + } else { getRowsSqlite(sqlite, processRows); } if (callback) callback(); @@ -1535,16 +1418,36 @@ function getSettingsfromDialog() { port: document.getElementById("txtPort").value, themeIndex: el.options[el.selectedIndex].value, documents: document.getElementById("chkDocuments").checked, - dbType: (document.getElementById("optSqlite").checked) ? "Sqlite" : "MySql" + dbType: (document.getElementById("optSqlite").checked) ? "Sqlite" : "MySql", + dbFile: settingsdbFile, + spellChecking: document.getElementById("chkSpelling").checked }; return settings; } +function getFilename(fullPath){ + let paths = fullPath.split("/"); + return paths[paths.length-1]; +} + +function getPath(fullPath){ + let paths = fullPath.split("/"); + return paths.slice(0, paths.length-1).join("/"); +} + /// Test the connection to the database using the settings entered into the dialog box. function testDBConnection() { var settings = getSettingsfromDialog(); - var connection = mysql.createConnection(settings); + var dbSettings = { + host: settings.host, + user: settings.user, + password: settings.password, + port: settings.port + } + showWaitImage(); + var connection = mysql.createConnection(dbSettings); connection.connect(function (err) { + hideWaitImage(); if (err) { alert("Database connection failed."); } else { @@ -1553,8 +1456,20 @@ function testDBConnection() { }); } +function createNotesDB() { + var settings = getSettingsfromDialog(); + var dbSettings = { + host: settings.host, + user: settings.user, + password: settings.password, + port: settings.port + } + var createDBSql = "CREATE SCHEMA `" + settings.database + "` ;"; + return execSqlQuery(dbSettings, createDBSql); +} + /// Create Calendar Notes DB Tables -function createNotesDBTables(callback) { +async function createNotesDBTables(callback) { var createNotesTable = "CREATE TABLE `Notes` ("; createNotesTable += "`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,"; createNotesTable += "`NoteDate` date NOT NULL,"; @@ -1572,13 +1487,31 @@ function createNotesDBTables(callback) { createTasksTable += "UNIQUE KEY `ID_UNIQUE` (`ID`)"; createTasksTable += ") ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;"; + var createDocsTable = "CREATE TABLE `Docs` ("; + createDocsTable += "`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,"; + createDocsTable += "`DocName` varchar(200) DEFAULT NULL,"; + createDocsTable += "`DocLocation` varchar(1000) DEFAULT NULL,"; + createDocsTable += "`DocColor` int(11) DEFAULT '16777215',"; + createDocsTable += "`DocText` varchar(20000) DEFAULT NULL,"; + createDocsTable += "`LastModified` datetime NOT NULL,"; + createDocsTable += "`DocIndentLevel` int(11) NOT NULL DEFAULT '0',"; + createDocsTable += "`DocOrder` int(11) NOT NULL DEFAULT '0',"; + createDocsTable += "`PageOrder` int(11) NOT NULL DEFAULT '0',"; + createDocsTable += "PRIMARY KEY (`ID`),"; + createDocsTable += "UNIQUE KEY `ID_UNIQUE` (`ID`)"; + createDocsTable += + ") ENGINE=InnoDB AUTO_INCREMENT=1048 DEFAULT CHARSET=latin1;"; var settings = getSettingsfromDialog(); - - createSQLTable(settings, createNotesTable, function (err) { - createSQLTable(settings, createTasksTable, function (err) { - if (callback) callback(err); - }); - }); + var err; + try { + await execSqlQuery(settings, createNotesTable); + await execSqlQuery(settings, createTasksTable); + await execSqlQuery(settings, createDocsTable); + } + catch(e){ + err = e; + } + if (callback) callback(err); } function initSettingsIcon() { @@ -1614,15 +1547,53 @@ function toggleSettingsBox() { } } -function updateDBSelection(elSelected){ - (elSelected == "optSqlite") ? document.getElementById("optMySql").checked = false : document.getElementById("optSqlite").checked = false +function updateDBSelection(elSelected) { + if (elSelected == "optSqlite") { + document.getElementById("optMySql").checked = false; + } + else{ + document.getElementById("optSqlite").checked = false; + } var optSqlite = document.getElementById("optSqlite"); var mySqlEls = document.querySelectorAll("tr[db='mysql']"); - for (var el of mySqlEls){ - (optSqlite.checked) ? el.classList.add("hide") : el.classList.remove("hide"); + for (let el of mySqlEls) { + (optSqlite.checked) ? el.classList.add("hide"): el.classList.remove("hide"); + } + var mySqliteEls = document.querySelectorAll("tr[db='sqlite']"); + for (let el of mySqliteEls) { + (optSqlite.checked) ? el.classList.remove("hide"): el.classList.add("hide"); } } +function getdbFilename(path) { + return new Promise((resolve, reject) => { + const options = { + defaultPath: path ? path : "./", + filters: [{ + name: 'Sqlite DB', + extensions: ['db'] + }, + { + name: 'All Files', + extensions: ['*'] + } + ] + }; + const result = dialog.showSaveDialogSync(null, options); + if (result) { + console.log(result); + resolve(result); + } + }); +} + +function setSpellChecking(checkSpelling){ + let chkSpell = checkSpelling ? "true" : "false"; + let textAreas = document.querySelectorAll("textarea"); + for (let textArea of textAreas){ + textArea.setAttribute("spellCheck", chkSpell); + } +} // #endregion SETTINGS CODE // #region RESIZE SIDE BARS @@ -1643,9 +1614,13 @@ function initVDrag(e) { function doVDrag(e) { if (dragTargetDiv === leftDiv) { - leftDiv.style.width = (startWidth + e.clientX - startX) + 'px'; + let newWidth = startWidth + e.clientX - startX; + if (newWidth < 230) newWidth = 230; + leftDiv.style.width = `${newWidth}px`; } else { - rightDiv.style.width = (startWidth + startX - e.clientX) + 'px'; + let newWidth = startWidth + startX - e.clientX; + if (newWidth < 150) newWidth = 150; + rightDiv.style.width = `${newWidth}px`; } } @@ -1656,7 +1631,7 @@ function stopVDrag(e) { saveWidths(); } -function saveWidths(){ +function saveWidths() { if (dragTargetDiv === leftDiv) { appSettings.setSettingInFile("leftSideBarWidth", leftDiv.style.width); } else { @@ -1664,6 +1639,10 @@ function saveWidths(){ } } +function loadWidths() { + rightDiv.style.width = appSettings.getSettingsInFile("docsSideBarWidth"); +} + // #endregion RESIZE SIDE BARS // #region HELPER FUNCTIONS @@ -1690,17 +1669,103 @@ function ShowWarningMessageBox(message) { dialog.showMessageBox(null, options); } +function showConfirmationBox(message) { + const options = { + type: "info", + title: "Confirm", + buttons: ["Yes", "No", "Cancel"], + message: message, + }; + + let response = dialog.showMessageBoxSync(null, options); + + return response == 0; +} + +function emptyDiv(divById) { + document.getElementById(divById).innerHTML = ""; +} + +function addItemtoDiv(divById, itemInnerText, classAdd, customData) { + var newItem = document.createElement("div"); + newItem.innerText = itemInnerText; + var classes = classAdd.split(" "); + for (var i = 0; i < classes.length; i++) { + newItem.classList.add(classes[i]); + } + if (customData) { + newItem.setAttribute(customData.split("=")[0], customData.split("=")[1]) + } + document.getElementById(divById).appendChild(newItem); + return newItem; +} + +function getDocChanged() { + return document.getElementById("btnSave").innerHTML == "*SAVE*"; +} + // #endregion HELPER FUNCTIONS +// #region WAIT IMAGE FUNCTIONS +function showWaitImage () { + // This will display the wait image in the center of the calling window. + numWaiting += 1; + console.log("waiting = " + numWaiting ); + if (!!document.getElementById("imgWaitImage")) { + return; //The image is already being displayed. + } + var waitImagePath = 'file://' + APPDIR + '/images/waitImg.gif'; + var waitImg = document.createElement("img"); + waitImg.id = "imgWaitImage"; + waitImg.src = waitImagePath; + document.body.appendChild(waitImg); + console.log("Added wait image."); +}; + +function hideWaitImage() { + // This will hide the wait image. + numWaiting -= 1; + if (numWaiting <= 0) { + var waitImg = document.getElementById("imgWaitImage"); + if (!!waitImg) document.body.removeChild(waitImg); + numWaiting = 0; + } + console.log("Waiting = " + numWaiting); +}; +// #endregion WAIT IMAGE FUNCTIONS + +// #region DOM HELPER FUNCTIONS +function locateMenu(elMenu, x, y){ + // This function properly locates the context menu so that it stays in the window. + elMenu.classList.remove("hide"); + let elW = elMenu.offsetWidth; + let docW = document.body.clientWidth; + let posX = x; + if (elW + x > docW - 5) posX = docW - elW - 5; + + let elH = elMenu.offsetHeight; + let docH = document.body.clientHeight; + let posY = y; + if (elH + y > docH - 5) posY = docH - elH - 5; + + elMenu.style.left = posX + "px"; + elMenu.style.top = posY + "px"; +} + +// #endregion DOM HELPER FUNCTIONS + // #region DOCUMENT EVENT HANDLERS document.getElementById("btnNow").addEventListener("click", function () { gotoDate(getNow()); }); document.getElementById("btnSave").addEventListener("click", function () { - saveNotes(lastDaySelected, document.getElementById("txtNotes").value); + if (document.getElementById("btnDocs").classList.contains("tabSelected")) { + app_documents.savePage(); + } else { + saveNotes(lastDaySelected, document.getElementById("txtNotes").value); + } saveTasks(document.getElementById("txtTasks").value); - //initWidths(); }); document.getElementById("btnRevert").addEventListener("click", function () { @@ -1724,7 +1789,6 @@ document.getElementById("btnDocs").addEventListener("click", function () { }); document.getElementById("btnGo").addEventListener("click", function () { - //addSearchResultItem("Test"); var searchText = document.getElementById("txtSearch").value; console.log("Searching for " + searchText); clearSearchResults(searchNotes(searchText)); @@ -1747,11 +1811,11 @@ document.getElementById("btnHideLeft").addEventListener("click", function () { } }); -document.getElementById("optSqlite").addEventListener("change", (e) =>{ +document.getElementById("optSqlite").addEventListener("change", (e) => { updateDBSelection(e.target.id); }); -document.getElementById("optMySql").addEventListener("change", (e) =>{ +document.getElementById("optMySql").addEventListener("change", (e) => { updateDBSelection(e.target.id); }); @@ -1775,23 +1839,29 @@ async function dateSelected(dayNum) { if (daySelected != dayNum) { //} && (!initialLoad)){ var lastDayClicked = document.getElementById("day" + daySelected); - if (lastDayClicked){ + if (lastDayClicked) { lastDayClicked.classList.remove("dateSelected"); } } + daySelected = dayNum; console.log(getSelectedDate()); // Save the notes for the last selected date. if (lastDaySelected != getSelectedDate() && !initialLoad) { - console.log("Saving notes - " + lastDaySelected); - saveNotes(lastDaySelected, document.getElementById("txtNotes").value); - saveTasks(document.getElementById("txtTasks").value); + if (getDocChanged){ + let notes = document.getElementById("txtNotes").value; + let tasks = document.getElementById("txtTasks").value; + await Promise.all([ + saveNotes(lastDaySelected, notes), + saveTasks(tasks) + ]); + } } await getNotes(getSelectedDate()); blockInterface = false; getTasks(); lastDaySelected = getSelectedDate(); - + initialLoad = false; } @@ -1825,19 +1895,29 @@ document.getElementById("btnSettings").addEventListener("click", function () { document .getElementById("btnSettingsClose") - .addEventListener("click", function () { + .addEventListener("click", async function () { _settings = getSettingsfromDialog(); - _settings.documents ? document.getElementById("btnDocs").classList.remove("hide") - : document.getElementById("btnDocs").classList.add("hide"); - appSettings.setSettingsInFile(_settings, (err) => { - if (!err) { - dateSelected(lastDaySelected); - } - else{ - console.log("Error saving settings file."); - } - }); - + _settings.documents ? document.getElementById("btnDocs").classList.remove("hide") : + document.getElementById("btnDocs").classList.add("hide"); + await appSettings.setSettingsInFile(_settings); + dbFile = settingsdbFile; + if (!fs.existsSync(settingsdbFile)){ + console.log("Creating database."); + await createSqliteDB(); + } + setSpellChecking(_settings.spellChecking); + getNotes(getSelectedDate()); + getTasks(); + if (_settings.documents) { + document.getElementById("btnDocs").classList.remove("hide"); + app_documents.loadDocs(true); + } else { + document.getElementById("btnDocs").classList.add("hide"); + }; + if (document.getElementById("btnViewMD").classList.contains("btnSelected")){ + mdViewSelected(); + } + $("#settingsSlider").animate({ right: "-200px" }); @@ -1864,6 +1944,18 @@ document }); }); +document + .getElementById("btnCreateDB") + .addEventListener("click", function () { + createNotesDB() + .then(()=>{ + alert("Database created."); + }) + .catch((err)=>{ + alert("Database creation failed."); + }); + }); + document.getElementById("selThemes").addEventListener("change", function () { var el = document.getElementById("selThemes"); var themeIndex = el.options[el.selectedIndex].value; @@ -1893,10 +1985,7 @@ document.getElementById("txtDoc").addEventListener("input", () => { document.getElementById("txtNotes").addEventListener("contextmenu", (e) => { console.log(e.clientX); var menu = document.querySelector(".notesMenu"); - menu.style.left = e.clientX + "px"; - menu.style.top = e.clientY + "px"; - menu.classList.remove("hide"); - console.log(menu); + locateMenu(menu, e.clientX, e.clientY); }); // document.getElementById("btnHideMenu").addEventListener("click", (e)=>{ @@ -1921,6 +2010,17 @@ document.getElementById("btnInsertTable").addEventListener("click", (e) => { document.querySelector("body").addEventListener("click", () => { document.querySelector(".notesMenu").classList.add("hide"); document.querySelector(".docsMenu").classList.add("hide"); + document.querySelector(".pagesMenu").classList.add("hide"); + document.getElementById("txtRename").classList.add("hide"); +}); + +document.addEventListener("keyup", (e) => { + if (e.key == "Escape") { + document.querySelector(".notesMenu").classList.add("hide"); + document.querySelector(".docsMenu").classList.add("hide"); + document.querySelector(".pagesMenu").classList.add("hide"); + document.getElementById("txtRename").classList.add("hide"); + } }); // Intercept the tab key while in the txtNotes area. @@ -1944,32 +2044,54 @@ document.querySelector("#txtNotes").addEventListener('keydown', function (e) { // The notes have been changed. Indicate this with the Save button stars. document.getElementById("btnSave").innerHTML = "*SAVE*"; - // prevent the focus lose + // prevent the focus loss e.preventDefault(); } }, false); -document.getElementById("vSplitter").addEventListener("mousedown", initVDrag, false); -document.getElementById("vSplitterDoc").addEventListener("mousedown", initVDrag, false); +document.querySelector("#txtDoc").addEventListener('keydown', function (e) { + if (e.keyCode === 9) { // tab was pressed + // get caret position/selection + var start = this.selectionStart; + var end = this.selectionEnd; -document.getElementById("btnAddDoc").addEventListener("click", ()=>{ - addDocLocation("","New Document"); -}); + var target = e.target; + var value = target.value; -// #region DOC CONTEXT MENU EVENT HANDLERS -document.getElementById("btnAddSubDoc").addEventListener("click", ()=>{ - addDocLocation(contextSelectedDoc,"New Document"); -}); + // set textarea value to: text before caret + tab + text after caret + target.value = value.substring(0, start) + + "\t" + + value.substring(end); -document.getElementById("btnRenameDoc").addEventListener("click", ()=>{ - renameDoc(contextSelectedDoc); -}); + // put caret at right position again (add one for the tab) + this.selectionStart = this.selectionEnd = start + 1; + + // The notes have been changed. Indicate this with the Save button stars. + document.getElementById("btnSave").innerHTML = "*SAVE*"; + + // prevent the focus loss + e.preventDefault(); + } +}, false); -document.getElementById("btnRemoveDoc").addEventListener("click", ()=>{ - removeDoc(contextSelectedDoc); +document.getElementById("btnBrowsedbFile").addEventListener("click", async ()=>{ + getdbFilename(getPath(dbFile)) + .then((fullPath)=>{ + if (!fullPath.endsWith(".db")) fullPath += ".db"; + console.log(fullPath); + settingsdbFile = fullPath; + document.getElementById("lbldbFile").innerHTML = getFilename(settingsdbFile); + document.getElementById("lbldbFile").title = settingsdbFile; + }); }); -// #endregion DOC CONTEXT MENU EVENT HANDLERS +//#region RESIZABLE SPLITTERS +document.getElementById("vSplitter").addEventListener("mousedown", initVDrag, false); + +document.getElementById("vSplitterDoc").addEventListener("mousedown", (e) => { + initVDrag(e); +}, false); +//#endregion RESIZABLE SPLITTERS // #endregion DOCUMENT EVENT HANDLERS \ No newline at end of file diff --git a/div_treeview.css b/div_treeview.css index b803b5f..a0b2c82 100644 --- a/div_treeview.css +++ b/div_treeview.css @@ -12,12 +12,15 @@ align-items: center; flex: 0 0 auto; padding: 5px; - background: var(--notesBGColor); + background-color: var(--compColor2); min-height: 30px; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; + border: var(--buttonbordersize); + border-style: solid; + border-color: lightgray; } .div_treeview_leftMargin { @@ -25,8 +28,11 @@ } .div_treeview_item { - color: var(--notesTextColor); + color: #000; padding: 2px 2px 2px 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .div_treeview_item:hover { @@ -35,8 +41,17 @@ color: var(--buttonhovertext); } +.div_treeview_text { + color: #000; + padding: 2px 2px 2px 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + pointer-events: none; +} + .div_treeview_selected { - background-color: var(--compColor1); + background-color: var(--buttonhover); color: black; } @@ -52,9 +67,10 @@ .div_treeview_marker { margin-top: 2px; margin-right: 5px; - width: 10px; + width: 0px; + min-width: 0px; height: 10px; - background-color: var(--compColor1); + background-color: var(--buttonface); } .div_treeview_marker:hover { @@ -83,8 +99,8 @@ width: 0 !important; height: 0!important; border-left: 10px solid transparent; - border-right: 0px solid var(--notesTextColor); - border-bottom: 10px solid var(--notesTextColor); + border-right: 0px solid var(--buttonface); + border-bottom: 10px solid var(--buttonface); background-color: transparent !important; } .div_treeview_arrow_dnrt:hover { @@ -97,7 +113,7 @@ width: 0 !important; height: 0!important; border-top: 5px solid transparent; - border-left: 10px solid var(--notesTextColor); + border-left: 10px solid var(--buttonface); border-right: 0px solid transparent; border-bottom: 5px solid transparent; background-color: transparent !important; diff --git a/div_treeview.js b/div_treeview.js index 700a1a8..8e8d274 100644 --- a/div_treeview.js +++ b/div_treeview.js @@ -7,10 +7,12 @@ function div_treeview(divTVElement, divTVDelimeter) { function addTVText(parent, text) { var newItem = document.createElement("div"); + newItem.setAttribute("draggable", "true"); //newItem.innerText = text; newItem.classList.add("div_treeview_item"); newItem.classList.add("div_treeview_hbox"); - newItem.innerHTML = "
" + text; + newItem.innerHTML = "
"; + newItem.innerHTML += "
" + text + "
"; parent.appendChild(newItem); } @@ -71,21 +73,65 @@ function div_treeview(divTVElement, divTVDelimeter) { } } + function getSelectedElement(){ + return document.querySelector(".div_treeview_selected"); + } + + function getSelectedFullPath(){ + let el = getSelectedElement(); + if (el){ + return getFullPath(el); + } + else{ + return null; + } + } + + function setSelectedPath(fullPath) { + if (divTVElement.innerHTML != ""){ + removeAllSelected(divTVElement); + let paths = document.querySelectorAll(".div_treeview_item"); + console.log(paths); + for (let item of paths){ + console.log(getFullPath(item)); + if (getFullPath(item) == fullPath){ + item.classList.add("div_treeview_selected"); + if (onSelect_Callback) onSelect_Callback(getFullPath(item)); + } + } + console.log(fullPath); + if (fullPath) expandToSelected(); + } + } + + function expandToSelected(){ + let selected = getSelectedElement(); + let prevItem = selected.parentNode; + while (prevItem != divTVElement){ + expand(prevItem); + prevItem = prevItem.parentNode; + } + } + function onSelect(callback){ onSelect_Callback = callback; divTVElement.ownerDocument.addEventListener("click", (e)=>{ - console.log(e.target); - if (e.target.classList.contains("div_treeview_item")){ + let el = e.target; + console.log(el); + if (el.classList.contains("div_treeview_text")){ + el = el.parentNode; + } + if (el.classList.contains("div_treeview_item")){ removeAllSelected(divTVElement); - e.target.classList.add("div_treeview_selected"); - callback(getFullPath(e.target)); + el.classList.add("div_treeview_selected"); + callback(getFullPath(el)); } - else if (e.target.classList.contains(_mkStyle)){ - if (e.target.classList.contains(_collapsedStyle)){ - expand(e.target.parentNode); + else if (el.classList.contains(_mkStyle)){ + if (el.classList.contains(_collapsedStyle)){ + expand(el.parentNode); } else{ - collapse(e.target.parentNode); + collapse(el.parentNode); } } }); @@ -102,25 +148,37 @@ function div_treeview(divTVElement, divTVDelimeter) { function onDblClick(callback){ divTVElement.ownerDocument.addEventListener("dblclick", (e)=>{ - if (e.target.classList.contains("div_treeview_item")){ - if (e.target.classList.contains("div_treeview_children_hidden")){ - expand(e.target); + let el = e.target; + if (el.classList.contains("div_treeview_text")){ + el = el.parentNode; + } + if (el.classList.contains("div_treeview_item")){ + if (el.classList.contains("div_treeview_children_hidden")){ + expand(el); } else{ - collapse(e.target); + collapse(el); } - callback(getFullPath(e.target)); + callback(getFullPath(el)); } }); } function onRightClick(callback){ divTVElement.ownerDocument.addEventListener("contextmenu", (e)=>{ - if (e.target.classList.contains("div_treeview_item")){ + let el = e.target; + if (el.classList.contains("div_treeview_text")){ + el = el.parentNode; + } + if (el.classList.contains("div_treeview_item")){ + if (!el.classList.contains("div_treeview_selected")){ + removeAllSelected(divTVElement); + el.classList.add("div_treeview_selected"); + } if (onSelect_Callback){ - onSelect_Callback(getFullPath(e.target)); + onSelect_Callback(getFullPath(el)); } - callback(e, getFullPath(e.target)); + callback(e, getFullPath(el)); } }); } @@ -128,7 +186,6 @@ function div_treeview(divTVElement, divTVDelimeter) { function getFullPath(divItem){ var fullPath = []; var noParent = false; - var divParent; while(!noParent){ console.log("Found innertext = '" + divItem.innerText.trim() + "'"); fullPath.unshift(divItem.innerText.trim()); //Inner text will have the marker on it. @@ -203,16 +260,18 @@ function div_treeview(divTVElement, divTVDelimeter) { } function expand(divItem){ - console.log("expanding:"); - console.log(divItem); - divItem.classList.remove("div_treeview_children_hidden"); - var marker = divItem.children[0]; - marker.classList.add(_expandedStyle); - marker.classList.remove(_collapsedStyle); - var divParent = divItem.parentNode; - var children = divParent.children; - for (var i=0; i
-
+
@@ -110,7 +110,13 @@
-
+ ADD DOC...
+
+
+ ADD DOC
+
+
+
+
+
-
+
@@ -130,16 +136,24 @@
-
+
+ +
-
+
+ +
-
+
+
+ ADD PAGE
+
+
+
@@ -159,7 +173,7 @@
-
+
@@ -172,6 +186,14 @@ + + + + + + + + + + + + -
APPLY/CLOSE
+
+
.calenderDB
+
...
+
+
@@ -216,6 +238,11 @@
TEST CONNECTION
+
CREATE DATABASE
+
CREATE TABLES
@@ -232,6 +259,13 @@
+
Spell Checking

@@ -243,8 +277,17 @@
Documents
+
+
+
+
+
v1.1.6
+
+
+
APPLY/CLOSE
+
+
@@ -257,16 +300,30 @@
ADD DOC
RENAME
REMOVE
-
CUT
-
PASTE
+
MOVE UP
+
MOVE DOWN
+
+
+
RENAME
+
REMOVE
+
INCREASE INDENT
+
DECREASE INDENT
+
MOVE UP
+
MOVE DOWN
+ + + + + + - diff --git a/rename.js b/rename.js deleted file mode 100644 index 7e96a33..0000000 --- a/rename.js +++ /dev/null @@ -1,22 +0,0 @@ -const electron = require("electron"); -const remote = require('electron').remote; -const libAppSettings = require("lib-app-settings"); - -const settingsFile = "./.settings"; -var appSettings = new libAppSettings(settingsFile); - -document.getElementById("btnCancel").addEventListener("click", () =>{ - var window = remote.getCurrentWindow(); - window.close(); -}); - -function init(){ - await appSettings.loadSettingsFromFile() - .then((settings)=>{ - changeTheme(settings.themeIndex); - }) - .catch((err)=>{ - }); -} - -init(); \ No newline at end of file diff --git a/renameItem.html b/renameItem.html deleted file mode 100644 index e0a59cd..0000000 --- a/renameItem.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - Rename - - - - -
-
-
-
Old Name:
-
-
-
-
- -
-
New Name:
-
- -
-
- -
- -
-
-
Cancel
-
-
Accept
-
-
-
-
- - - diff --git a/screenshots/Screenshot_Application.png b/screenshots/Screenshot_Application.png deleted file mode 100644 index 69e7b5a..0000000 Binary files a/screenshots/Screenshot_Application.png and /dev/null differ diff --git a/screenshots/Screenshot_Search.png b/screenshots/Screenshot_Search.png deleted file mode 100644 index 54e847b..0000000 Binary files a/screenshots/Screenshot_Search.png and /dev/null differ diff --git a/screenshots/Screenshot_Settings.png b/screenshots/Screenshot_Settings.png deleted file mode 100644 index 0b10b25..0000000 Binary files a/screenshots/Screenshot_Settings.png and /dev/null differ diff --git a/screenshots/add_document.png b/screenshots/add_document.png new file mode 100644 index 0000000..c886484 Binary files /dev/null and b/screenshots/add_document.png differ diff --git a/screenshots/add_page.png b/screenshots/add_page.png new file mode 100644 index 0000000..226de17 Binary files /dev/null and b/screenshots/add_page.png differ diff --git a/screenshots/document_added.png b/screenshots/document_added.png new file mode 100644 index 0000000..5228be5 Binary files /dev/null and b/screenshots/document_added.png differ diff --git a/screenshots/fullwindow_notes.png b/screenshots/fullwindow_notes.png new file mode 100644 index 0000000..6c81e6a Binary files /dev/null and b/screenshots/fullwindow_notes.png differ diff --git a/screenshots/markdown.png b/screenshots/markdown.png new file mode 100644 index 0000000..e4a288c Binary files /dev/null and b/screenshots/markdown.png differ diff --git a/screenshots/modify_document.png b/screenshots/modify_document.png new file mode 100644 index 0000000..1ee620d Binary files /dev/null and b/screenshots/modify_document.png differ diff --git a/screenshots/mysql_settings.png b/screenshots/mysql_settings.png new file mode 100644 index 0000000..3291d12 Binary files /dev/null and b/screenshots/mysql_settings.png differ diff --git a/screenshots/no_settings_warning.png b/screenshots/no_settings_warning.png new file mode 100644 index 0000000..96640b5 Binary files /dev/null and b/screenshots/no_settings_warning.png differ diff --git a/screenshots/not_markdown.png b/screenshots/not_markdown.png new file mode 100644 index 0000000..c15dd9b Binary files /dev/null and b/screenshots/not_markdown.png differ diff --git a/screenshots/search_results.png b/screenshots/search_results.png new file mode 100644 index 0000000..2c29797 Binary files /dev/null and b/screenshots/search_results.png differ diff --git a/screenshots/settings_documents.png b/screenshots/settings_documents.png new file mode 100644 index 0000000..b2756b7 Binary files /dev/null and b/screenshots/settings_documents.png differ diff --git a/screenshots/sqlite_settings.png b/screenshots/sqlite_settings.png new file mode 100644 index 0000000..c1d0dbc Binary files /dev/null and b/screenshots/sqlite_settings.png differ diff --git a/screenshots/theme_clu.png b/screenshots/theme_clu.png new file mode 100644 index 0000000..e3baa75 Binary files /dev/null and b/screenshots/theme_clu.png differ diff --git a/screenshots/theme_cool.png b/screenshots/theme_cool.png new file mode 100644 index 0000000..bc69ec3 Binary files /dev/null and b/screenshots/theme_cool.png differ diff --git a/screenshots/theme_default.png b/screenshots/theme_default.png new file mode 100644 index 0000000..10edcde Binary files /dev/null and b/screenshots/theme_default.png differ diff --git a/screenshots/theme_green.png b/screenshots/theme_green.png new file mode 100644 index 0000000..e360d77 Binary files /dev/null and b/screenshots/theme_green.png differ diff --git a/screenshots/theme_pink.png b/screenshots/theme_pink.png new file mode 100644 index 0000000..e1db5de Binary files /dev/null and b/screenshots/theme_pink.png differ diff --git a/screenshots/theme_warm.png b/screenshots/theme_warm.png new file mode 100644 index 0000000..2b1e240 Binary files /dev/null and b/screenshots/theme_warm.png differ