diff --git a/README.md b/README.md index 9f5a0ce..15313a8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@

- +

@@ -27,7 +27,7 @@ Iris is written in TypeScript and Rust. -The app currently in early development and may not be 100% stable for daily use. +The app is currently in early development and may not be 100% stable for daily use. Only macOS installers are supplied in the early development phase. Other platforms will be supported in the official release. diff --git a/package.json b/package.json index 35f93ad..c68ddef 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "iris", - "version": "0.2.0-dev-4.3", + "version": "0.2.0-dev-5.0", "productName": "Iris", - "description": "Iris is a comfortable Markdown note-taking app", + "description": "A comfortable note-taking app powered by Markdown", "main": "./out/main/index.js", "author": "alexwkleung", "license": "MIT", diff --git a/src/main/index.ts b/src/main/index.ts index a9fa2bb..4838e6d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,130 +1,209 @@ -import { app, shell, BrowserWindow, protocol, net, ipcMain } from 'electron' -import { join } from 'path' -import { dirname } from 'path' +import { app, shell, BrowserWindow, protocol, net, ipcMain, dialog } from 'electron' +import { join, dirname } from 'path' import { fileURLToPath } from 'url' import contextMenu from 'electron-context-menu' //import icon from '../../resources/icon.png?asset' import { isMacOS, isWindows, isLinux, isDev } from './is-main' import windowStateKeeper from 'electron-window-state' -import { dialog } from 'electron' -//prevent multiple instances of Iris running -if(!app.requestSingleInstanceLock()) { - console.log("Another instance of Iris is running. Exiting."); - - dialog.showErrorBox("Iris", "Another instance of Iris is running. Closing current instance."); - - app.quit(); -} - -ipcMain.handle('error-dialog', (event, title, content) => { - dialog.showErrorBox(title, content); -}) - -function createWindow(): void { - //esm version of __dirname - const _dirname: string = dirname(fileURLToPath(import.meta.url)); - - //isomorphic version of __dirname for both cjs/esm compatibility (https://antfu.me/posts/isomorphic-dirname) - //const _dirname: string = typeof __dirname !== 'undefined' ? __dirname : dirname(fileURLToPath(import.meta.url)); - - //create window state keeper - const windowState: windowStateKeeper.State = windowStateKeeper({ - defaultWidth: 1200, - defaultHeight: 900, - }) - - let mainWindow: BrowserWindow = {} as BrowserWindow; - - mainWindow = new BrowserWindow({ - width: windowState.width, - height: windowState.height, - x: windowState.x, - y: windowState.y, - minWidth: 800, - minHeight: 600, - show: false, - autoHideMenuBar: true, - titleBarStyle: isMacOS() ? 'hiddenInset' : 'default', //only check macOS - webPreferences: { - preload: join(_dirname, '../preload/index.js'), - sandbox: false, - contextIsolation: true, - webviewTag: false, - spellcheck: false +namespace MainProcess { + class AppWindow { + /** + * Browser window + * + * @protected + */ + protected mainWindow: BrowserWindow = {} as BrowserWindow; + + /** + * Window state + * + * @private + */ + private windowState: windowStateKeeper.State = {} as windowStateKeeper.State; + + /** + * App instance + * + * @private + */ + private appInstance(): void { + //prevent multiple instances of Iris running + if(!app.requestSingleInstanceLock()) { + console.log("Another instance of Iris is running. Exiting."); + + dialog.showErrorBox("Iris", "Another instance of Iris is running. Closing current instance."); + + app.quit(); + process.exit(0); + } } - }); - - //register windowState listener - windowState.manage(mainWindow); - - //check if platform is darwin - if(isMacOS()) { - //log - console.log("Platform is darwin (macOS)"); - //check if platform is linux - } else if(isLinux()) { - //log - console.log("Platform is Linux"); - //check if platform is windows - } else if(isWindows()) { - //log - console.log("Platform is Windows"); - } - //set min window size - mainWindow.setMinimumSize(800, 600); + /** + * ipcMain handlers + * + * @private + */ + private ipcMainHandlers(): void { + ipcMain.handle('error-dialog', (_: Electron.IpcMainInvokeEvent, title: string, content: string) => { + dialog.showErrorBox(title, content); + }) + + ipcMain.handle('show-message-box', (_: Electron.IpcMainInvokeEvent, message: string) => { + dialog.showMessageBox(this.mainWindow, { + message: message + }) + }) + } - //for now, use electron-context-menu and extend it if necessary. - //later on, I will implement my own custom context menu so it can be - //fine-tuned based on the needs of Iris - contextMenu(); + /** + * Call this after registering `windowState` listener + * + * @internal + * @private + */ + private mainProcLogger(): void { + console.log("Current window dimensions: " + this.windowState.width + "x" + this.windowState.height); + console.log("Current window coordinates: " + "(" + this.windowState.x + ", " + this.windowState.y + ")"); + } - mainWindow.on('ready-to-show', () => { - mainWindow.show(); - }); + /** + * Initialize main window + * + * @private + */ + private initializeMainWindow(): void { + //esm version of __dirname + const _dirname: string = dirname(fileURLToPath(import.meta.url)); + + //isomorphic version of __dirname for both cjs/esm compatibility (https://antfu.me/posts/isomorphic-dirname) + //const _dirname: string = typeof __dirname !== 'undefined' ? __dirname : dirname(fileURLToPath(import.meta.url)); + + this.windowState = windowStateKeeper({ + defaultWidth: 1200, + defaultHeight: 900, + }); - mainWindow.webContents.setWindowOpenHandler((details) => { - shell.openExternal(details.url) - return { - action: 'deny' + this.mainWindow = new BrowserWindow({ + width: this.windowState.width, + height: this.windowState.height, + x: this.windowState.x, + y: this.windowState.y, + minWidth: 800, + minHeight: 600, + show: false, + autoHideMenuBar: true, + titleBarStyle: isMacOS() ? 'hiddenInset' : 'default', //only check macOS + webPreferences: { + preload: join(_dirname, '../preload/index.js'), + sandbox: false, + contextIsolation: true, + webviewTag: false, + spellcheck: false + } + }); + + //register windowState listener + this.windowState.manage(this.mainWindow); + + //main process logger + this.mainProcLogger(); + + //check if platform is darwin + if(isMacOS()) { + //log + console.log("Platform is darwin (macOS)"); + //check if platform is linux + } else if(isLinux()) { + //log + console.log("Platform is Linux"); + //check if platform is windows + } else if(isWindows()) { + //log + console.log("Platform is Windows"); } - }); - - if(isDev()) { - //load dev server url in main window - mainWindow.loadURL('http://localhost:5173/'); + + //set min window size + this.mainWindow.setMinimumSize(800, 600); + + //for now, use electron-context-menu and extend it if necessary. + //later on, I will implement my own custom context menu so it can be + //fine-tuned based on the needs of Iris + contextMenu(); + + this.mainWindow.on('ready-to-show', () => { + this.mainWindow.show(); + }); + + this.mainWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { + action: 'deny' + } + }); + + if(isDev()) { + //load dev server url in main window + this.mainWindow.loadURL('http://localhost:5173/'); + + //open dev tools undocked by default + this.mainWindow.webContents.openDevTools({ + mode: 'undocked' + }); + } else { + //this is supposed to be production build + //path might change once electron-vite is removed + this.mainWindow.loadFile(join(__dirname, '../renderer/index.html')); + } + } - //open dev tools undocked by default - mainWindow.webContents.openDevTools({ - mode: 'undocked' + /** + * Initialize Electron app + * + * @private + */ + private initializeElectronApp(): void { + app.whenReady().then(() => { + //initialize main window + this.initializeMainWindow(); + + app.on('activate', () => { + if(BrowserWindow.getAllWindows().length === 0) { + this.initializeMainWindow(); + } + }); + + //custom protocol to handle local file system absolute paths + protocol.handle('local', (request): Promise => { + return net.fetch('file://' + request.url.slice('local://'.length)).catch((e) => console.error(e)) as Promise + }) + }); + + app.on('window-all-closed', () => { + if(isLinux() || isWindows()) { + app.quit(); + } else if(isMacOS()) { + return; + } }); - } else { - //this is supposed to be production build - //path might change once electron-vite is removed - mainWindow.loadFile(join(__dirname, '../renderer/index.html')); + } + + /** + * Build main window + * + * @public + */ + public buildMainWindow(): void { + this.appInstance(); + this.ipcMainHandlers(); + this.initializeElectronApp(); + } } + + //AppWindow object + export const ElectronWindow: AppWindow = new AppWindow(); } -app.whenReady().then(() => { - createWindow(); +//build window +MainProcess.ElectronWindow.buildMainWindow(); - app.on('activate', () => { - if(BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } - }); - - //custom protocol to handle local file system absolute paths - protocol.handle('local', (request): Promise => { - return net.fetch('file://' + request.url.slice('local://'.length)).catch((e) => console.error(e)) as Promise - }) -}); - -app.on('window-all-closed', () => { - if(isLinux() || isWindows()) { - app.quit(); - } else if(isMacOS()) { - return; - } -}); \ No newline at end of file diff --git a/src/preload/index.ts b/src/preload/index.ts index 70b2162..b44550c 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -3,45 +3,74 @@ import { electronAPI } from '../preload/electron/electron' import { fsMod } from './mod/fs-mod' import { settingFiles } from '../renderer/src/settings/create-default-settings' -function appStartDirectoryCheck(): void { - if( - !fsMod._isPathDir(fsMod._baseDir("home") + "/Iris") - && !fsMod._isPathDir(fsMod._baseDir("home") + "/Iris/Notes") - && !fsMod._isPathDir(fsMod._baseDir("home") + "/Iris/Images") - ) { - console.log("directories don't exist"); - - //create iris directory - fsMod._createDir(fsMod._baseDir("home") + "/Iris"); - - //create notes directory - fsMod._createDir(fsMod._baseDir("home") + "/Iris/Notes"); - - //create images directory - fsMod._createDir(fsMod._baseDir("home") + "/Iris/Images"); - - //create default settings - settingFiles.createSettingFile('default'); +namespace PreloadProcess { + class PreloadScripts { + /** + * App start directory check + * + * @private + */ + private appStartDirectoryCheck(): void { + if(!fsMod._isPathDir(fsMod._baseDir("home") + "/Iris") && !fsMod._isPathDir(fsMod._baseDir("home") + "/Iris/Notes") && !fsMod._isPathDir(fsMod._baseDir("home") + "/Iris/Images")) { + console.log("directories don't exist"); + + //create iris directory + fsMod._createDir(fsMod._baseDir("home") + "/Iris"); + + //create notes directory + fsMod._createDir(fsMod._baseDir("home") + "/Iris/Notes"); + + //create images directory + fsMod._createDir(fsMod._baseDir("home") + "/Iris/Images"); + + //create default settings + settingFiles.createSettingFile('default'); + } } -} -appStartDirectoryCheck(); -if(process.contextIsolated) { - try { - //expose electron - contextBridge.exposeInMainWorld('electron', electronAPI) + /** + * Context isolation + * + * @private + */ + private contextIsolation(): void { + if(process.contextIsolated) { + try { + //expose electron + contextBridge.exposeInMainWorld('electron', electronAPI) + + //expose fsMod + contextBridge.exposeInMainWorld('fsMod', fsMod) + } catch(e) { + throw console.error(e); + } + } else { + //eslint-disable-next-line + //@ts-ignore + window.electron = electronAPI - //expose fsMod - contextBridge.exposeInMainWorld('fsMod', fsMod) - } catch(e) { - throw console.error(e); + //eslint-disable-next-line + //@ts-ignore + window.fsMod = fsMod + } + } + + /** + * Preload scripts + * + * @public + */ + public preloadScripts(): void { + this.appStartDirectoryCheck(); + this.contextIsolation(); + } } -} else { - //eslint-disable-next-line - //@ts-ignore - window.electron = electronAPI - //eslint-disable-next-line - //@ts-ignore - window.fsMod = fsMod -} \ No newline at end of file + /** + * PreloadScripts object + */ + export const execute: PreloadScripts = new PreloadScripts(); +} + +//execute preload scripts +PreloadProcess.execute.preloadScripts(); \ No newline at end of file diff --git a/src/renderer/assets/classic-light.min.css b/src/renderer/assets/classic-light.min.css index 1ca3619..687a8e9 100644 --- a/src/renderer/assets/classic-light.min.css +++ b/src/renderer/assets/classic-light.min.css @@ -4,4 +4,4 @@ License: ~ MIT (or more permissive) [via base16-schemes-source] Maintainer: @highlightjs/core-team Version: 2021.09.0 -*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#303030;background:#d2d1d1}.hljs ::selection,.hljs::selection{background-color:#d0d0d0;color:#303030}.hljs-comment{color:#b0b0b0}.hljs-tag{color:#505050}.hljs-operator,.hljs-punctuation,.hljs-subst{color:#303030}.hljs-operator{opacity:.7}.hljs-bullet,.hljs-deletion,.hljs-name,.hljs-selector-tag,.hljs-template-variable,.hljs-variable{color:#ac4142}.hljs-attr,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:#d28445}.hljs-class .hljs-title,.hljs-title,.hljs-title.class_{color:#f4bf75}.hljs-strong{font-weight:700;color:#f4bf75}.hljs-addition,.hljs-code,.hljs-string,.hljs-title.class_.inherited__{color:#90a959}.hljs-built_in,.hljs-doctag,.hljs-keyword.hljs-atrule,.hljs-quote,.hljs-regexp{color:#75b5aa}.hljs-attribute,.hljs-function .hljs-title,.hljs-section,.hljs-title.function_,.ruby .hljs-property{color:#6a9fb5}.diff .hljs-meta,.hljs-keyword,.hljs-template-tag,.hljs-type{color:#aa759f}.hljs-emphasis{color:#aa759f;font-style:italic}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#8f5536}.hljs-meta .hljs-keyword,.hljs-meta-keyword{font-weight:700} \ No newline at end of file +*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#303030;background:#d2d1d1}.hljs ::selection,.hljs::selection{background-color:#d0d0d0;color:#303030}.hljs-comment{color:#b0b0b0}.hljs-tag{color:#505050}.hljs-operator,.hljs-punctuation,.hljs-subst{color:#303030}.hljs-operator{opacity:.7}.hljs-bullet,.hljs-deletion,.hljs-name,.hljs-selector-tag,.hljs-template-variable,.hljs-variable{color:#ac4142}.hljs-attr,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:#d28445}.hljs-class .hljs-title,.hljs-title,.hljs-title.class_{color:#f0a946}.hljs-strong{font-weight:700;color:#f4bf75}.hljs-addition,.hljs-code,.hljs-string,.hljs-title.class_.inherited__{color:#90a959}.hljs-built_in,.hljs-doctag,.hljs-keyword.hljs-atrule,.hljs-quote,.hljs-regexp{color:#75b5aa}.hljs-attribute,.hljs-function .hljs-title,.hljs-section,.hljs-title.function_,.ruby .hljs-property{color:#6a9fb5}.diff .hljs-meta,.hljs-keyword,.hljs-template-tag,.hljs-type{color:#aa759f}.hljs-emphasis{color:#aa759f;font-style:italic}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#8f5536}.hljs-meta .hljs-keyword,.hljs-meta-keyword{font-weight:700} \ No newline at end of file diff --git a/src/renderer/assets/global.css b/src/renderer/assets/global.css index a61bc0a..1ab893c 100644 --- a/src/renderer/assets/global.css +++ b/src/renderer/assets/global.css @@ -722,8 +722,8 @@ body { #settings-modal-exit { z-index: 1; position: fixed; - width: 42px; - height: 25px; + width: 35px; + height: 27px; color: white; border: 1px solid; border-color: grey; diff --git a/src/renderer/listener-main.ts b/src/renderer/listener-main.ts index d6eb1c7..efa6b96 100644 --- a/src/renderer/listener-main.ts +++ b/src/renderer/listener-main.ts @@ -3,7 +3,6 @@ import { DirectoryTreeUIModalListeners } from "./src/event-listeners/directory-t import { SettingsModalListeners } from "./src/event-listeners/settings-modal-listeners.js" import { DirectoryTreeKebabDropdownListeners } from "./src/event-listeners/directory-tree-kebab-dropdown-listener.js" -//eslint-disable-next-line @typescript-eslint/no-namespace export namespace ListenerNs { export function directoryTreeListeners(): void { const dirTreeListeners = new DirectoryTreeListeners(); diff --git a/src/renderer/src/event-listeners/directory-tree-ui-modal-listeners.ts b/src/renderer/src/event-listeners/directory-tree-ui-modal-listeners.ts index f705f07..45ecf72 100644 --- a/src/renderer/src/event-listeners/directory-tree-ui-modal-listeners.ts +++ b/src/renderer/src/event-listeners/directory-tree-ui-modal-listeners.ts @@ -22,6 +22,8 @@ import { Settings } from "../settings/settings" import { AdvancedModeSettings } from "../settings/settings" import { ReadingMode } from "../mode/reading-mode" import { markdownParser } from "../utils/markdown-parser" +import { GenericEvent } from "./event" +import { KeyBinds } from "../keybinds/keybinds" /** * @extends DirectoryTreeUIModals @@ -91,444 +93,322 @@ export class DirectoryTreeUIModalListeners extends DirectoryTreeUIModals impleme private readonly dirTreeStateListeners = new DirectoryTreeStateListeners(); /** - * Create file modal exit listener + * File name for create file input + * @private */ - public createFileModalExitListener(): void { - DirectoryTreeUIModals.createModalExitButton.addEventListener('click', () => { - const createFileNode: NodeListOf = document.querySelectorAll('.create-new-file'); + private fileName: string = "" + ".md"; - createFileNode.forEach((elem) => { - //null check - if(elem !== null) { - elem.classList.remove('is-active-create-new-file-modal'); - } - }); - - //null check - if(DirectoryTreeUIModals.createModalContainer !== null) { - DirectoryTreeUIModals.createModalContainer.remove(); - } - //invoke parent root listener - this.directoryTreeListeners.parentRootListener(); - - //invoke child node listener - this.directoryTreeListeners.childNodeListener(); - - //invoke parent root listener again so the directory tree will be in sync - this.directoryTreeListeners.parentRootListener(); - }) - } + /** + * Folder name for create folder input + * @private + */ + private folderName: string = ""; /** - * Create file modal continue listener + * Create modal exit callback * - * @param el Element to attach `keyup` event listener to + * @public */ - public createFileModalContinueListener(el: HTMLElement): void { - let fileName: string = "" + ".md"; + public createModalExitCb: () => void = (): void => { + const createFileNode: NodeListOf = document.querySelectorAll('.create-new-file'); - el.addEventListener('keyup', (e) => { - //assign current value of input element on keyup + extension - fileName = ((e.target as HTMLInputElement).value).trim() + ".md"; + createFileNode.forEach((elem) => { + //null check + if(elem !== null) { + elem.classList.remove('is-active-create-new-file-modal'); + } + }); - //log - console.log(fileName); - }) + //null check + if(DirectoryTreeUIModals.createModalContainer !== null) { + DirectoryTreeUIModals.createModalContainer.remove(); + } + + //invoke parent root listener + this.directoryTreeListeners.parentRootListener(); - DirectoryTreeUIModals.createModalContinueButton.addEventListener('click', async () => { - if(fileName === ".md") { - window.electron.ipcRenderer.invoke('error-dialog', "Iris", "A note with an empty file name has been created. It is recommended to rename the note.") - } + //invoke child node listener + this.directoryTreeListeners.childNodeListener(); - //mode check - if(isModeBasic() && fileName !== " ") { - //log - console.log((document.querySelector('#create-file-modal-folder-name-input-node') as HTMLElement).textContent) - - fsMod.fs._createFile( - fsMod.fs._baseDir("home") - + "/Iris/Notes/" - + (document.querySelector('#create-file-modal-folder-name-input-node') as HTMLElement).textContent - + "/" + fileName, - "# " + fileName.split('.md')[0] + " " + '\n' - ); - - const createFileModalFolderNameRef: string = (document.querySelector('#create-file-modal-folder-name-input-node') as HTMLElement).textContent as string; - - const createFileNode: NodeListOf = document.querySelectorAll('.create-new-file'); - - createFileNode.forEach((elem) => { - //null check - if(elem !== null) { - elem.classList.remove('is-active-create-new-file-modal'); - } - }); + //invoke parent root listener again so the directory tree will be in sync + this.directoryTreeListeners.parentRootListener(); - //null check - if(DirectoryTreeUIModals.createModalContainer !== null) { - DirectoryTreeUIModals.createModalContainer.remove(); - } + //dispose + GenericEvent.use.disposeEvent(DirectoryTreeUIModals.createModalExitButton, 'click', this.createModalExitCb, undefined, "Disposed event for create modal exit"); - //log - console.log(fileName); - - //if document contains at least one active child - if(document.querySelector('.is-active-child')) { - //select all active children and remove them from the dom (active status) - //this removes any existing active children files - document.querySelectorAll('.is-active-child').forEach( - (isActiveChild) => { - if(isActiveChild !== null) { - isActiveChild.classList.remove('is-active-child') - } - } - ); - } + GenericEvent.use.disposeEvent(window, 'keydown', KeyBinds.map.bindCb, undefined, "Disposed event for create modal exit (keydown escape)"); - const childFile: HTMLDivElement = document.createElement('div'); - childFile.setAttribute("class", "child-file-name is-active-child"); + KeyBinds.map.resetMapList(); + } - const childFileTextNode: Text = document.createTextNode(fileName.split('.md')[0]); + /** + * Create file modal exit listener + * + * @public + */ + public createFileModalExitListener(): void { + GenericEvent.use.createDisposableEvent(DirectoryTreeUIModals.createModalExitButton, 'click', this.createModalExitCb, undefined, "Created disposable event for create modal exit"); - document.querySelectorAll('.parent-folder-name').forEach((el) => { - //this doesn't cover duplicate folder names, so it might cause bugs - if(el.textContent === createFileModalFolderNameRef) { - childFile.appendChild(childFileTextNode); + KeyBinds.map.bind(this.createModalExitCb, "Escape", false); + } - (el.parentNode as ParentNode).appendChild(childFile); - } - }) - - //execute parent root listener so it understands the new file - this.directoryTreeListeners.parentRootListener(); + /** + * Create file modal input callback + * + * @param e Event + * + * @public + */ + public createFileModalInputCb: (e: Event) => void = (e: Event): void => { + //assign current value of input element on keyup + extension + this.fileName = ((e.target as HTMLInputElement).value).trim() + ".md"; + + //log + console.log(this.fileName); + } - //execute child node listener to allow new file to be clicked - this.directoryTreeListeners.childNodeListener(); + /** + * Create file modal continue callback + * + * @async + * @public + */ + public createFileModalContinueCb: () => Promise = async (): Promise => { + if(this.fileName === ".md") { + window.electron.ipcRenderer.invoke('show-message-box', "Note name cannot be empty. Enter a valid note name."); - //execute parent root listener again so everything will be in sync - this.directoryTreeListeners.parentRootListener(); + return; + } - //destroy current editor view - PMEditorView.editorView.destroy(); - - //create new editor view - PMEditorView.createEditorView(); + if(this.fileName !== ".md") { + fsMod.fs._createFile( + fsMod.fs._baseDir("home") + + "/Iris/Notes/" + + (document.querySelector('#create-file-modal-folder-name-input-node') as HTMLElement).textContent + + "/" + this.fileName, + "# " + this.fileName.split('.md')[0] + " " + '\n' + ); + } - //log - console.log(createFileModalFolderNameRef); + const createFileModalFolderNameRef: string = (document.querySelector('#create-file-modal-folder-name-input-node') as HTMLElement).textContent as string; + const createFileNode: NodeListOf = document.querySelectorAll('.create-new-file'); - //log - console.log(fileName); - - //update editor view state - PMEditorView.editorView.updateState( - //apply transaction - PMEditorView.editorView.state.apply( - //since editor gets destroyed and re-created, the - //range is 0 to 0 - PMEditorState.editorState.tr.replaceRangeWith( - 0, - 0, - defaultMarkdownParser.parse( - fsMod.fs._readFileFolder(createFileModalFolderNameRef, - fileName - ) - ) as Node - ))); - - //set contenteditable - PMEditorView.setContenteditable(true); - - //if contenteditable attribute is set to true - if((document.querySelector('.ProseMirror') as HTMLElement).getAttribute('contenteditable') === 'true') { - //show the menubar - (document.querySelector('.ProseMirror-menubar') as HTMLElement).style.display = ""; - } + createFileNode.forEach((elem) => { + //null check + if(elem !== null) { + elem.classList.remove('is-active-create-new-file-modal'); + } + }); - (document.getElementById('kebab-dropdown-menu-container') as HTMLElement).style.display = ""; - - //invoke auto save listener - this.editorListeners.autoSaveListener("prosemirror"); - - //invoke insert tab listener - this.editorListeners.insertTabListener((document.querySelector('.ProseMirror') as HTMLElement), 2); - - //assign references to corresponding key properties - RefsNs.currentParentChildData.map((props) => { - //log - console.log(fileName); - //log - console.log(document.querySelector('.child-file-name.is-active-child') as HTMLElement); - - //null check - if(props !== null) { - //child file name - props.childFileName = fileName.split('.md')[0]; - props.childFileNode = document.querySelector('.child-file-name.is-active-child') as HTMLElement + if(document.querySelector('.is-active-child')) { + //select all active children and remove them from the dom (active status) + //this removes any existing active children files + document.querySelectorAll('.is-active-child').forEach( + (isActiveChild) => { + if(isActiveChild !== null) { + isActiveChild.classList.remove('is-active-child') } - }); - - //apply active state listener - this.dirTreeStateListeners.activeChildFileStateListener(); + } + ); + } - //word count listener - wordCountListener("prosemirror"); + //null check + if(DirectoryTreeUIModals.createModalContainer !== null) { + DirectoryTreeUIModals.createModalContainer.remove(); + } - //kebab dropdown menu listener - this.editorkebabDropdownMenuListeners.kebabDropdownMenuListener(); + const childFile: HTMLDivElement = document.createElement('div'); + childFile.setAttribute("class", "child-file-name is-active-child"); - //change document title so it corresponds to the opened file - await setWindowTitle("Iris", true, createFileModalFolderNameRef + " - " + fileName.split('.md')[0]).catch((e) => { throw console.error(e) }); + const childFileTextNode: Text = document.createTextNode(this.fileName.split('.md')[0]); - //add directory info to editor top bar - this.editorTopBarContainer.directoryInfo(); + //assign references to corresponding key properties + RefsNs.currentParentChildData.map((props) => { + //log + console.log(this.fileName); + //log + console.log(document.querySelector('.child-file-name.is-active-child') as HTMLElement); + + //null check + if(props !== null) { + //child file name + props.childFileName = this.fileName.split('.md')[0]; + props.childFileNode = document.querySelector('.child-file-name.is-active-child') as HTMLElement + } + }); - (document.getElementById('file-directory-kebab-dropdown-menu-container') as HTMLElement).style.display = ""; + document.querySelectorAll('.parent-folder-name').forEach((el) => { + //this doesn't cover duplicate folder names, so it might cause bugs + if(el.textContent === createFileModalFolderNameRef) { + childFile.appendChild(childFileTextNode); - //to-do: sort files... - } else if(isModeAdvanced() && fileName !== " ") { - //log - console.log((document.querySelector('#create-file-modal-folder-name-input-node') as HTMLElement).textContent) + (el.parentNode as ParentNode).appendChild(childFile); + } + }) - fsMod.fs._createFile( - fsMod.fs._baseDir("home") - + "/Iris/Notes/" - + (document.querySelector('#create-file-modal-folder-name-input-node') as HTMLElement).textContent - + "/" + fileName, - "# " + fileName.split('.md')[0] + " " + '\n' - ); - - const createFileModalFolderNameRef: string = (document.querySelector('#create-file-modal-folder-name-input-node') as HTMLElement).textContent as string; - - const createFileNode: NodeListOf = document.querySelectorAll('.create-new-file'); - - createFileNode.forEach((elem) => { - //null check - if(elem !== null) { - elem.classList.remove('is-active-create-new-file-modal'); - } - }); + //execute parent root listener so it understands the new file + this.directoryTreeListeners.parentRootListener(); - //null check - if(DirectoryTreeUIModals.createModalContainer !== null) { - DirectoryTreeUIModals.createModalContainer.remove(); - } + //execute child node listener to allow new file to be clicked + this.directoryTreeListeners.childNodeListener(); - //log - console.log(fileName); - - //if document contains at least one active child - if(document.querySelector('.is-active-child')) { - //select all active children and remove them from the dom (active status) - //this removes any existing active children files - document.querySelectorAll('.is-active-child').forEach( - (isActiveChild) => { - if(isActiveChild !== null) { - isActiveChild.classList.remove('is-active-child') - } - } - ); - } + //execute parent root listener again so everything will be in sync + this.directoryTreeListeners.parentRootListener(); - const childFile: HTMLDivElement = document.createElement('div'); - childFile.setAttribute("class", "child-file-name is-active-child"); + //apply active state listener + this.dirTreeStateListeners.activeChildFileStateListener(); - const childFileTextNode: Text = document.createTextNode(fileName.split('.md')[0]); + //mode check + if(isModeBasic()) { + //log + console.log(this.fileName); - document.querySelectorAll('.parent-folder-name').forEach((el) => { - //this doesn't cover duplicate folder names, so it might cause bugs - if(el.textContent === createFileModalFolderNameRef) { - childFile.appendChild(childFileTextNode); + //destroy current editor view + PMEditorView.editorView.destroy(); + + //create new editor view + PMEditorView.createEditorView(); - (el.parentNode as ParentNode).appendChild(childFile); - } - }) + //log + console.log(createFileModalFolderNameRef); + + //log + console.log(this.fileName); + + //update editor view state + PMEditorView.editorView.updateState( + //apply transaction + PMEditorView.editorView.state.apply( + //since editor gets destroyed and re-created, the + //range is 0 to 0 + PMEditorState.editorState.tr.replaceRangeWith( + 0, + 0, + defaultMarkdownParser.parse( + fsMod.fs._readFileFolder(createFileModalFolderNameRef, + this.fileName + ) + ) as Node + ))); + + //set contenteditable + PMEditorView.setContenteditable(true); - //execute parent root listener so it understands the new file - this.directoryTreeListeners.parentRootListener(); + //if contenteditable attribute is set to true + if((document.querySelector('.ProseMirror') as HTMLElement).getAttribute('contenteditable') === 'true') { + //show the menubar + (document.querySelector('.ProseMirror-menubar') as HTMLElement).style.display = ""; + } - //execute child node listener to allow new file to be clicked - this.directoryTreeListeners.childNodeListener(); + (document.getElementById('kebab-dropdown-menu-container') as HTMLElement).style.display = ""; - //execute parent root listener again so everything will be in sync - this.directoryTreeListeners.parentRootListener(); + //invoke auto save listener + this.editorListeners.autoSaveListener("prosemirror"); - //destroy current editor view - CMEditorView.editorView.destroy(); - - //create new editor view - CMEditorView.createEditorView(); + //invoke insert tab listener + this.editorListeners.insertTabListener((document.querySelector('.ProseMirror') as HTMLElement), 2); - //log - console.log(createFileModalFolderNameRef); + //word count listener + wordCountListener("prosemirror"); - //log - console.log(fileName); - - //dispatch text insertion tr - CMEditorView.editorView.dispatch({ - changes: { - from: 0, - to: 0, - insert: fsMod.fs._readFileFolder(createFileModalFolderNameRef, fileName) - } - }) + //kebab dropdown menu listener + this.editorkebabDropdownMenuListeners.kebabDropdownMenuListener(); + } else if(isModeAdvanced()) { + //log + console.log(this.fileName); - //set contenteditable - CMEditorView.setContenteditable(true); + //destroy current editor view + CMEditorView.editorView.destroy(); + + //create new editor view + CMEditorView.createEditorView(); - //cursor theme - if(Settings.getSettings.lightTheme) { - CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[0]) }) - } else if(Settings.getSettings.darkTheme) { - CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[1]) }) - } + //log + console.log(createFileModalFolderNameRef); - //check block cursor - if(Settings.getSettings.defaultCursor && Settings.getSettings.lightTheme) { - AdvancedModeSettings.defaultCursor("light"); - } else if(Settings.getSettings.defaultCursor && Settings.getSettings.darkTheme) { - AdvancedModeSettings.defaultCursor("dark"); - } else if( - Settings.getSettings.blockCursor && Settings.getSettings.lightTheme - || Settings.getSettings.blockCursor && Settings.getSettings.darkTheme - ) { - AdvancedModeSettings.blockCursor(); + //log + console.log(this.fileName); + + //dispatch text insertion tr + CMEditorView.editorView.dispatch({ + changes: { + from: 0, + to: 0, + insert: fsMod.fs._readFileFolder(createFileModalFolderNameRef, this.fileName) } + }) - (document.getElementById('kebab-dropdown-menu-container') as HTMLElement).style.display = ""; - - //invoke auto save listener - this.editorListeners.autoSaveListener("codemirror"); - - //assign references to corresponding key properties - RefsNs.currentParentChildData.map((props) => { - //log - console.log(fileName); - //log - console.log(document.querySelector('.child-file-name.is-active-child') as HTMLElement); - - //null check - if(props !== null) { - //child file name - props.childFileName = fileName.split('.md')[0]; - props.childFileNode = document.querySelector('.child-file-name.is-active-child') as HTMLElement - } - }); - - //apply active state listener - this.dirTreeStateListeners.activeChildFileStateListener(); + //set contenteditable + CMEditorView.setContenteditable(true); - //word count listener - wordCountListener("codemirror"); + //cursor theme + if(Settings.getSettings.lightTheme) { + CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[0]) }) + } else if(Settings.getSettings.darkTheme) { + CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[1]) }) + } - //kebab dropdown menu listener - this.editorkebabDropdownMenuListeners.kebabDropdownMenuListener(); + //check block cursor + if(Settings.getSettings.defaultCursor && Settings.getSettings.lightTheme) { + AdvancedModeSettings.defaultCursor("light"); + } else if(Settings.getSettings.defaultCursor && Settings.getSettings.darkTheme) { + AdvancedModeSettings.defaultCursor("dark"); + } else if(Settings.getSettings.blockCursor && Settings.getSettings.lightTheme || Settings.getSettings.blockCursor && Settings.getSettings.darkTheme) { + AdvancedModeSettings.blockCursor(); + } - //change document title so it corresponds to the opened file - await setWindowTitle("Iris", true, createFileModalFolderNameRef + " - " + fileName.split('.md')[0]).catch((e) => { throw console.error(e) }); + (document.getElementById('kebab-dropdown-menu-container') as HTMLElement).style.display = ""; - //add directory info to editor top bar - this.editorTopBarContainer.directoryInfo(); + //invoke auto save listener + this.editorListeners.autoSaveListener("codemirror"); - (document.getElementById('file-directory-kebab-dropdown-menu-container') as HTMLElement).style.display = ""; - } else if(isModeReading() && fileName !== " ") { - fsMod.fs._createFile( - fsMod.fs._baseDir("home") - + "/Iris/Notes/" - + (document.querySelector('#create-file-modal-folder-name-input-node') as HTMLElement).textContent - + "/" + fileName, - "# " + fileName.split('.md')[0] + " " + '\n' - ); + //word count listener + wordCountListener("codemirror"); - const createFileModalFolderNameRef: string = (document.querySelector('#create-file-modal-folder-name-input-node') as HTMLElement).textContent as string; - const createFileNode: NodeListOf = document.querySelectorAll('.create-new-file'); + //kebab dropdown menu listener + this.editorkebabDropdownMenuListeners.kebabDropdownMenuListener(); + } else if(isModeReading()) { + //remove reading mode container if present in DOM + if((document.getElementById('reading-mode-container') as HTMLElement) !== null) { + (document.getElementById('reading-mode-container') as HTMLElement).remove(); + } - createFileNode.forEach((elem) => { - //null check - if(elem !== null) { - elem.classList.remove('is-active-create-new-file-modal'); - } - }); + //create reading mode node + ReadingMode.readingModeNode(); - //null check - if(DirectoryTreeUIModals.createModalContainer !== null) { - DirectoryTreeUIModals.createModalContainer.remove(); - } + //create fragment and append + const content: string = await markdownParser(fsMod.fs._readFileFolder(createFileModalFolderNameRef, this.fileName)).catch((e) => { throw console.error(e) }); + const contextFragment = new Range().createContextualFragment(content); + (document.getElementById('reading-mode-content') as HTMLElement).appendChild(contextFragment); + } - //if document contains at least one active child - if(document.querySelector('.is-active-child')) { - //select all active children and remove them from the dom (active status) - //this removes any existing active children files - document.querySelectorAll('.is-active-child').forEach( - (isActiveChild) => { - if(isActiveChild !== null) { - isActiveChild.classList.remove('is-active-child') - } - } - ); - } + //change document title so it corresponds to the opened file + await setWindowTitle("Iris", true, createFileModalFolderNameRef + " - " + this.fileName.split('.md')[0]).catch((e) => { throw console.error(e) }); - const childFile: HTMLDivElement = document.createElement('div'); - childFile.setAttribute("class", "child-file-name is-active-child"); + //add directory info to editor top bar + this.editorTopBarContainer.directoryInfo(); - const childFileTextNode: Text = document.createTextNode(fileName.split('.md')[0]); + (document.getElementById('file-directory-kebab-dropdown-menu-container') as HTMLElement).style.display = ""; - document.querySelectorAll('.parent-folder-name').forEach((el) => { - //this doesn't cover duplicate folder names, so it might cause bugs - if(el.textContent === createFileModalFolderNameRef) { - childFile.appendChild(childFileTextNode); + //dispose + GenericEvent.use.disposeEvent(DirectoryTreeUIModals.createModalContinueButton, 'click', this.createFileModalContinueCb, undefined, "Disposed create file modal continue event"); - (el.parentNode as ParentNode).appendChild(childFile); - } - }) - - //assign references to corresponding key properties - RefsNs.currentParentChildData.map((props) => { - //log - console.log(fileName); - //log - console.log(document.querySelector('.child-file-name.is-active-child') as HTMLElement); - - //null check - if(props !== null) { - //child file name - props.childFileName = fileName.split('.md')[0]; - props.childFileNode = document.querySelector('.child-file-name.is-active-child') as HTMLElement - } - }); - - //apply active state listener - this.dirTreeStateListeners.activeChildFileStateListener(); - - //change document title so it corresponds to the opened file - await setWindowTitle("Iris", true, createFileModalFolderNameRef + " - " + fileName.split('.md')[0]).catch((e) => { throw console.error(e) }); - - //add directory info to editor top bar - this.editorTopBarContainer.directoryInfo(); - - (document.getElementById('file-directory-kebab-dropdown-menu-container') as HTMLElement).style.display = ""; - - //remove reading mode container if present in DOM - if((document.getElementById('reading-mode-container') as HTMLElement) !== null) { - (document.getElementById('reading-mode-container') as HTMLElement).remove(); - } + GenericEvent.use.disposeEvent(window, 'keydown', KeyBinds.map.bindCb, undefined, "Disposed event for bind (keydown enter)"); + } - //create reading mode node - ReadingMode.readingModeNode(); + /** + * Create file modal continue listener + * + * @param el Element to attach `keyup` event listener to + */ + public createFileModalContinueListener(el: HTMLElement): void { + GenericEvent.use.createDisposableEvent(el, 'keyup', this.createFileModalInputCb, undefined, "Created disposable event for create file modal input"); - //create fragment and append - const content: string = await markdownParser(fsMod.fs._readFileFolder(createFileModalFolderNameRef, fileName)).catch((e) => { throw console.error(e) }); - const contextFragment = new Range().createContextualFragment(content); - (document.getElementById('reading-mode-content') as HTMLElement).appendChild(contextFragment); + GenericEvent.use.createDisposableEvent(DirectoryTreeUIModals.createModalContinueButton, 'click', this.createFileModalContinueCb, undefined, "Created disposable event for create file modal continue"); - //listeners - this.directoryTreeListeners.parentRootListener(); - this.directoryTreeListeners.childNodeListener(); - } - }) + KeyBinds.map.bind(this.createFileModalContinueCb, "Enter", false); } - + /** * Create file listener */ @@ -537,20 +417,16 @@ export class DirectoryTreeUIModalListeners extends DirectoryTreeUIModals impleme this.parentTags = document.querySelectorAll('.parent-of-root-folder'); this.parentNameTags = document.querySelectorAll('.parent-folder-name'); - + + //clean this up for(let i = 0; i < this.parentNameTags.length; i++) { - //when a parent name tag is clicked - this.parentNameTags[i].addEventListener('click', () => { + GenericEvent.use.createDisposableEvent(this.parentNameTags[i], 'click', () => { //check if parent tag contains is-active-parent class if(this.parentTags[i].classList.contains('is-active-parent')) { //toggle show-create-file class on create-new-file node createFileNode[i].classList.toggle('show-create-file'); - //when a create-new-file node is clicked - createFileNode[i].addEventListener('click', (e) => { - //need stopImmediatePropagation so parentNameTag listener doesn't conflict with the createFileNode listener - e.stopImmediatePropagation(); - + GenericEvent.use.createDisposableEvent(createFileNode[i], 'click', () => { if(createFileNode[i].classList.contains('show-create-file')) { //invoke create file modal this.createFileModal(); @@ -568,11 +444,6 @@ export class DirectoryTreeUIModalListeners extends DirectoryTreeUIModals impleme //invoke createFileModalCurrentFolderNode this.createFileModalCurrentFolderNode(props.parentFolderName); - - //log - //console.log(props.parentFolderName); - //log - //console.log(props.parentFolderNode); } }); @@ -589,155 +460,187 @@ export class DirectoryTreeUIModalListeners extends DirectoryTreeUIModals impleme } else if(!createFileNode[i].classList.contains('show-create-file')) { createFileNode[i].classList.remove('show-create-file'); } - }); + }) } else if(!this.parentTags[i].classList.contains('is-active-parent')) { createFileNode[i].classList.remove('show-create-file'); } - }); + }) } } - + /** - * Create folder continue listener + * Create folder input callback * - * @param el Element to attach the `keyup` event listener to + * @param e Event + * @public */ - public createFolderContinueListener(el: HTMLElement): void { - let folderName: string = ""; - - el.addEventListener('keyup', (e) => { - folderName = ((e.target as HTMLInputElement).value).trim(); - console.log(folderName); - }); - - //console.log(folderName); + public createFolderInputCb: (e: Event) => void = (e: Event): void => { + this.folderName = ((e.target as HTMLInputElement).value).trim(); + console.log(this.folderName); + } + /** + * Create folder modal continue callback + * + * @public + */ + public createFolderModalContinueCb: () => void = (): void => { let parentFolder: HTMLDivElement = {} as HTMLDivElement; - - DirectoryTreeUIModals.createModalContinueButton.addEventListener('click', () => { - //log - console.log(folderName); + + if(this.folderName === "" || this.folderName === " ") { + window.electron.ipcRenderer.invoke('show-message-box', "Folder name cannot be empty. Enter a valid folder name.") - if(folderName === "") { - window.electron.ipcRenderer.invoke('error-dialog', "Iris", "Folder name cannot be empty. Enter a valid folder name."); + return; + } else if(this.folderName !== "" || this.folderName !== " " as string) { + //create directory + fsMod.fs._createDir( + fsMod.fs._baseDir("home") + + "/Iris/Notes/" + + this.folderName + ); + } + + parentFolder = document.createElement('div'); + parentFolder.setAttribute("class", "parent-of-root-folder is-not-active-parent"); + (document.getElementById('file-directory-tree-container-inner') as HTMLElement).appendChild(parentFolder); - return; - } else if(folderName !== " ") { - //create directory - fsMod.fs._createDir( - fsMod.fs._baseDir("home") - + "/Iris/Notes/" - + folderName - ); - } + const parentFolderName: HTMLDivElement = document.createElement('div'); + parentFolderName.setAttribute("class", "parent-folder-name"); + parentFolder.appendChild(parentFolderName); + + const parentFolderTextNode: Text = document.createTextNode(this.folderName); + parentFolderName.appendChild(parentFolderTextNode); + + const parentFolderCaret: HTMLDivElement = document.createElement('div'); + parentFolderCaret.setAttribute("class", "parent-folder-caret"); + + const parentFolderCaretTextNode: Text = document.createTextNode(String.fromCharCode(94)); + parentFolderCaret.appendChild(parentFolderCaretTextNode); + parentFolder.appendChild(parentFolderCaret); + + const parentRoot: NodeListOf = document.querySelectorAll('.parent-of-root-folder'); + + for(let i = 0; i < parentRoot.length; i++) { + //invoke folder file count + this.folderFileCountObject.folderFileCount(parentRoot[i], this.directoryTreeListeners.parentNameTagsArr()[i], true); + } - parentFolder = document.createElement('div'); - parentFolder.setAttribute("class", "parent-of-root-folder is-not-active-parent"); - (document.getElementById('file-directory-tree-container-inner') as HTMLElement).appendChild(parentFolder); - - const parentFolderName: HTMLDivElement = document.createElement('div'); - parentFolderName.setAttribute("class", "parent-folder-name"); - parentFolder.appendChild(parentFolderName); - - const parentFolderTextNode: Text = document.createTextNode(folderName); - parentFolderName.appendChild(parentFolderTextNode); - - const parentFolderCaret: HTMLDivElement = document.createElement('div'); - parentFolderCaret.setAttribute("class", "parent-folder-caret"); + //invoke parent root listener (created directory only) + this.directoryTreeListeners.parentRootListener(); + + //invoke create file node + //bug + this.createFileNode(parentFolder); + this.createFileNode(parentFolder); + + //invoke create file listener + this.createFileListener(); + + //null check + if(DirectoryTreeUIModals.createModalContainer !== null) { + DirectoryTreeUIModals.createModalContainer.remove(); + } - const parentFolderCaretTextNode: Text = document.createTextNode(String.fromCharCode(94)); - parentFolderCaret.appendChild(parentFolderCaretTextNode); - parentFolder.appendChild(parentFolderCaret); + //invoke parent root listener again so entire directory tree will function normally and be in sync + //bug + this.directoryTreeListeners.parentRootListener(); + this.directoryTreeListeners.parentRootListener(); - const parentRoot: NodeListOf = document.querySelectorAll('.parent-of-root-folder'); + //dispose create file modal continue cb + GenericEvent.use.disposeEvent(DirectoryTreeUIModals.createModalContinueButton, 'click', this.createFileModalContinueCb, undefined, "Disposed event for create file modal continue") - for(let i = 0; i < parentRoot.length; i++) { - //invoke folder file count - this.folderFileCountObject.folderFileCount(parentRoot[i], this.directoryTreeListeners.parentNameTagsArr()[i], true); - } + //dispose bind cb + GenericEvent.use.disposeEvent(window, 'keydown', KeyBinds.map.bindCb, undefined, "Disposed event for bind (keydown enter)"); + + //dispose create folder input cb + GenericEvent.use.disposeEvent(document.body, 'keyup', this.createFolderInputCb, undefined, "Disposed event for create folder input"); + } - //invoke parent root listener (created directory only) + /** + * Create folder continue listener + * + * @param el Element to attach the `keyup` event listener to + */ + public createFolderContinueListener(el: HTMLElement): void { + GenericEvent.use.createDisposableEvent(el, 'keyup', this.createFolderInputCb, undefined, "Created disposable event for create folder input") + + GenericEvent.use.createDisposableEvent(DirectoryTreeUIModals.createModalContinueButton, 'click', this.createFolderModalContinueCb, undefined, "Created event for create folder modal continue") + + KeyBinds.map.bind(this.createFolderModalContinueCb, "Enter", false); + } + + /** + * Create folder modal exit callback + * @public + */ + public createFolderModalExitCb: () => void = (): void => { + if((document.getElementById('create-modal-container') as HTMLElement) !== null) { + (document.getElementById('create-modal-container') as HTMLElement).remove(); + } + + if(isModeBasic() || isModeAdvanced() || isModeReading()) { this.directoryTreeListeners.parentRootListener(); + this.createFileListener(); + } + + GenericEvent.use.setEventCallbackTimeout(() => { + //dispose create folder modal exit cb + GenericEvent.use.disposeEvent(DirectoryTreeUIModals.createModalExitButton, 'click', this.createFolderModalExitCb, undefined, "Disposed event for create folder modal exit (click)"); - //invoke create file node - this.createFileNode(parentFolder); + //dispose bind cb + GenericEvent.use.disposeEvent(window, 'keydown', KeyBinds.map.bindCb, undefined, "Disposed event for bind (keydown escape)"); - //invoke create file listener - this.createFileListener(); + //dispose create folder input cb + GenericEvent.use.disposeEvent(document.body, 'keyup', this.createFolderInputCb, undefined, "Disposed event for create folder input"); - //null check - if(DirectoryTreeUIModals.createModalContainer !== null) { - DirectoryTreeUIModals.createModalContainer.remove(); - } - - //invoke create file listener - //bug - this.createFileListener(); - this.createFileListener(); - - //invoke parent root listener again so entire directory tree will function normally and be in sync - //bug - this.directoryTreeListeners.parentRootListener(); - this.directoryTreeListeners.parentRootListener(); - }) + }, 150) + + KeyBinds.map.resetMapList(); } /** * Create folder modal exit listener */ - public createFolderModalExitListener(): void { - DirectoryTreeUIModals.createModalExitButton.addEventListener('click', () => { - //log - console.log("Clicked on exit button"); + public createFolderModalExitListener(): void { + GenericEvent.use.createDisposableEvent(DirectoryTreeUIModals.createModalExitButton, 'click', this.createFolderModalExitCb, undefined, "Created event for create folder modal exit (click)"); - if((document.getElementById('create-modal-container') as HTMLElement) !== null) { - (document.getElementById('create-modal-container') as HTMLElement).remove(); - } + KeyBinds.map.bind(this.createFolderModalExitCb, "Escape", false); + } + + /** + * Create folder callback + * + * @public + */ + public createFolderCb: () => void = (): void => { + GenericEvent.use.setEventCallbackTimeout( + () => { + //log + console.log("clicked create folder"); + + //invoke create folder modal + this.createFolderModal(); + + //invoke create modal exit listener + this.createFolderModalExitListener(); + + //mode check + if(isModeBasic() || isModeAdvanced() || isModeReading()) { + //invoke create folder continue listener + this.createFolderContinueListener((document.getElementById('create-folder-input-node') as HTMLElement)); + } + + //dispose create folder cb + GenericEvent.use.disposeEvent((document.getElementById('create-folder') as HTMLElement), 'click', () => GenericEvent.use.setEventCallbackTimeout(this.createFolderCb, 50), undefined, "Disposed event for create folder (click)"); + }, 50 + ) - //mode check - if(isModeBasic()) { - this.directoryTreeListeners.parentRootListener(); - - this.createFileListener(); - } else if(isModeAdvanced()) { - this.directoryTreeListeners.parentRootListener(); - - this.createFileListener(); - } else if(isModeReading()) { - this.directoryTreeListeners.parentRootListener(); - //invoke again (bug) - this.directoryTreeListeners.parentRootListener(); - - this.createFileListener(); - } - }) } /** * Create folder listener */ public createFolderListener(): void { - (document.getElementById('create-folder') as HTMLElement).addEventListener('click', (e) => { - e.stopImmediatePropagation(); - - //log - console.log("clicked create folder"); - - //invoke create folder modal - this.createFolderModal(); - - //invoke create modal exit listener - this.createFolderModalExitListener(); - - //mode check - if(isModeBasic()) { - //invoke create folder continue listener - this.createFolderContinueListener((document.getElementById('create-folder-input-node') as HTMLElement)); - } else if(isModeAdvanced()) { - this.createFolderContinueListener((document.getElementById('create-folder-input-node') as HTMLElement)); - } else if(isModeReading()) { - this.createFolderContinueListener((document.getElementById('create-folder-input-node') as HTMLElement)); - } - }) + GenericEvent.use.createDisposableEvent((document.getElementById('create-folder') as HTMLElement), 'click', () => GenericEvent.use.setEventCallbackTimeout(this.createFolderCb, 50), undefined, "Created event for create folder (click)"); } } diff --git a/src/renderer/src/event-listeners/event.ts b/src/renderer/src/event-listeners/event.ts index 951d29d..f2498e3 100644 --- a/src/renderer/src/event-listeners/event.ts +++ b/src/renderer/src/event-listeners/event.ts @@ -1,5 +1,5 @@ interface IEvent< - T extends HTMLElement, + T extends HTMLElement | Window & typeof globalThis | Element, Z extends string, K extends any | unknown, X extends boolean | undefined, @@ -9,13 +9,15 @@ interface IEvent< disposeEvent(el: T, type: Z, fn: (...args: K[]) => K, capture?: X, log?: Y): void; } +type TEvent = IEvent + export namespace GenericEvent { /** * Don't export * * @internal */ - class Event implements IEvent { + class Event implements TEvent { /** * Create dispoable event listener * @@ -25,7 +27,7 @@ export namespace GenericEvent { * @param useCapture - Optional - Default value is `false`. If `true`, it specifies that the event listener being removed is a capturing listener * @param log - Optional - Print a message to console */ - public createDisposableEvent(el: HTMLElement, type: string, listener: (...args: any[]) => any | unknown, useCapture?: boolean | undefined, log?: string | undefined): void { + public createDisposableEvent(el: HTMLElement | Window & typeof globalThis | Element, type: string, listener: (...args: any[]) => any | unknown, useCapture?: boolean | undefined, log?: string | undefined): void { if(typeof listener === 'function') { el.addEventListener(type, listener, useCapture); } @@ -46,7 +48,7 @@ export namespace GenericEvent { * @param capture Optional - Default value is `false`. If `true`, it specifies that the event listener being removed is a capturing listener * @param log Optional - Print a message to console */ - public disposeEvent(el: HTMLElement, type: string, fn: (...args: any[]) => any | unknown, capture?: boolean | undefined, log?: string | undefined): void { + public disposeEvent(el: HTMLElement | Window & typeof globalThis | Element, type: string, fn: (...args: any[]) => any | unknown, capture?: boolean | undefined, log?: string | undefined): void { if(typeof fn === 'function') { el.removeEventListener(type, fn, capture); } @@ -57,6 +59,18 @@ export namespace GenericEvent { return; } } + + + /** + * Set event callback timeout + * + * @param fn Reference callback function to execute after `ms` + * @param ms Number to wait until reference function gets executed + * @returns Timeout to be executed in event + */ + public setEventCallbackTimeout(fn: () => any | void, ms: number | undefined): NodeJS.Timeout { + return setTimeout(fn, ms); + } } /** diff --git a/src/renderer/src/event-listeners/file-directory-state-listener.ts b/src/renderer/src/event-listeners/file-directory-state-listener.ts index 7fac3f9..e4e56c6 100644 --- a/src/renderer/src/event-listeners/file-directory-state-listener.ts +++ b/src/renderer/src/event-listeners/file-directory-state-listener.ts @@ -1,6 +1,33 @@ import { RefsNs } from "./directory-tree-listeners" +import { GenericEvent } from "./event" export class DirectoryTreeStateListeners { + public el: Element = {} as Element; + + public activeChildFileStateCb: () => void = (): void => { + //doc title folder name + //trim any whitespace from the split if necessary + const docTitleFolderName: string = document.title.split('-')[1].trim(); + + const childFileName: Element[] | null = Array.from(document.getElementsByClassName('child-file-name')); + + //null check + if(childFileName !== null) { + for(let i = 0; i < childFileName.length; i++) { + //check if docTitleFolderName and docTitleFileName match the respective refs + if(docTitleFolderName === RefsNs.currentParentChildData[0].parentFolderName && childFileName[i].textContent === RefsNs.currentParentChildData[0].childFileName) { + //compare text content to make sure it's identical + if(childFileName[i].textContent === RefsNs.currentParentChildData[0].childFileName) { + //add is-active-child class to the child file + childFileName[i].classList.add('is-active-child'); + } else { + continue; + } + } + } + } + } + /** * Active child file state listener */ @@ -8,30 +35,13 @@ export class DirectoryTreeStateListeners { document.querySelectorAll('.parent-folder-name').forEach((el) => { //null check if(el !== null) { - el.addEventListener('click', () => { - //doc title folder name - //trim any whitespace from the split if necessary - const docTitleFolderName: string = document.title.split('-')[1].trim(); - - const childFileName: Element[] | null = Array.from(document.getElementsByClassName('child-file-name')); - - //null check - if(childFileName !== null) { - for(let i = 0; i < childFileName.length; i++) { - //check if docTitleFolderName and docTitleFileName match the respective refs - if(docTitleFolderName === RefsNs.currentParentChildData[0].parentFolderName && childFileName[i].textContent === RefsNs.currentParentChildData[0].childFileName) { - console.log(RefsNs.currentParentChildData[0].childFileNode.textContent); - //compare text content to make sure it's identical - if(childFileName[i].textContent === RefsNs.currentParentChildData[0].childFileName) { - //add is-active-child class to the child file - childFileName[i].classList.add('is-active-child'); - } else { - continue; - } - } - } - } - }); + this.el = el; + + console.log(this.el); + + GenericEvent.use.createDisposableEvent(this.el, 'click', () => GenericEvent.use.setEventCallbackTimeout(this.activeChildFileStateCb, 20), undefined, "Create disposable event for active child file state (click)"); + + return; } }); } diff --git a/src/renderer/src/event-listeners/kebab-dropdown-menu-listener.ts b/src/renderer/src/event-listeners/kebab-dropdown-menu-listener.ts index 67945b3..3f2ac5a 100644 --- a/src/renderer/src/event-listeners/kebab-dropdown-menu-listener.ts +++ b/src/renderer/src/event-listeners/kebab-dropdown-menu-listener.ts @@ -9,6 +9,7 @@ import { CMEditorView } from "../codemirror/editor/cm-editor-view" import { FolderFileCount } from "../misc-ui/folder-file-count" import { DirectoryTree } from "../file-directory-tree/file-directory" import { GenericEvent } from "./event" +import { KeyBinds } from "../keybinds/keybinds" /** * @extends EditorKebabDropdownModals @@ -17,11 +18,16 @@ export class EditorKebabDropdownMenuListeners extends EditorKebabDropdownModals private readonly folderFileCount = new FolderFileCount(); private readonly directoryTree = new DirectoryTree(); + private renameFile: string = ""; public kebabModalContainerCb: () => void = (): void => { EditorKebabDropdownModals.kebabModalContainerNode.remove(); GenericEvent.use.disposeEvent(EditorKebabDropdownModals.kebabModalExitButtonNode, 'click', this.kebabModalContainerCb, undefined, "Disposed kebab modal container event") + + GenericEvent.use.disposeEvent(window, "keydown", this.kebabModalContainerCb, undefined, "Disposed event for kebab modal container (keydown escape)"); + + KeyBinds.map.resetMapList(); } /** @@ -29,218 +35,254 @@ export class EditorKebabDropdownMenuListeners extends EditorKebabDropdownModals */ public kebabExitModalListener(): void { GenericEvent.use.createDisposableEvent(EditorKebabDropdownModals.kebabModalExitButtonNode, 'click', this.kebabModalContainerCb, undefined, "Created disposable event for kebab modal container event") + + KeyBinds.map.bind(this.kebabModalContainerCb, "Escape", false); } /** - * Kebab delete file continue modal listener + * Kebab delete file continue callback + * + * @public */ - public kebabDeleteFileContinueModalListener(): void { - EditorKebabDropdownModals.kebabModalContinueButtonNode.addEventListener('click', () => { - document.querySelectorAll('.child-file-name.is-active-child').forEach(async (el) => { - //mode check - if(isModeBasic()) { - //log - console.log( - fsMod.fs._baseDir("home") - + "/Iris/Notes/" - + document.title.split('-')[1].trim() - + "/" - + el.textContent + ".md" - ); - - fsMod.fs._deletePath( - fsMod.fs._baseDir("home") - + "/Iris/Notes/" - + document.title.split('-')[1].trim() - + "/" - + el.textContent + ".md" - ); - - //destroy editor and respawn - PMEditorView.editorView.destroy(); - PMEditorView.createEditorView(); - PMEditorView.setContenteditable(false); - - //hide prosemirror menubar - (document.querySelector('.ProseMirror-menubar') as HTMLElement).style.display = "none"; + public kebabDeleteFileContinueCb: () => void = (): void => { + document.querySelectorAll('.child-file-name.is-active-child').forEach(async (el) => { + //mode check + if(isModeBasic()) { + //log + console.log( + fsMod.fs._baseDir("home") + + "/Iris/Notes/" + + document.title.split('-')[1].trim() + + "/" + + el.textContent + ".md" + ); + + fsMod.fs._deletePath( + fsMod.fs._baseDir("home") + + "/Iris/Notes/" + + document.title.split('-')[1].trim() + + "/" + + el.textContent + ".md" + ); + + //destroy editor and respawn + PMEditorView.editorView.destroy(); + PMEditorView.createEditorView(); + PMEditorView.setContenteditable(false); + + //hide prosemirror menubar + (document.querySelector('.ProseMirror-menubar') as HTMLElement).style.display = "none"; + + const parentRoot: NodeListOf = document.querySelectorAll('.parent-of-root-folder'); + const parentNameTags: NodeListOf = document.querySelectorAll('.parent-folder-name'); - const parentRoot: NodeListOf = document.querySelectorAll('.parent-of-root-folder'); - const parentNameTags: NodeListOf = document.querySelectorAll('.parent-folder-name'); - - let count: number = 0; - //remove duplicate folder file count nodes - while(count <= 2) { - document.querySelectorAll('.folder-file-count-container').forEach((el) => { - el.remove(); - }); - count++; - } - - for(let i = 0; i < parentNameTags.length; i++) { - //invoke folder file count - this.folderFileCount.folderFileCount(parentRoot[i], this.directoryTree.parentNameTagsArr()[i], true); - } - - //hide file directory kebab dropdown menu container - (document.getElementById('file-directory-kebab-dropdown-menu-container') as HTMLElement).style.display = "none"; - } else if(isModeAdvanced()) { - fsMod.fs._deletePath( - fsMod.fs._baseDir("home") - + "/Iris/Notes/" - + document.title.split('-')[1].trim() - + "/" - + el.textContent + ".md" - ); + let count: number = 0; + //remove duplicate folder file count nodes + while(count <= 2) { + document.querySelectorAll('.folder-file-count-container').forEach((el) => { + el.remove(); + }); + count++; + } + + for(let i = 0; i < parentNameTags.length; i++) { + //invoke folder file count + this.folderFileCount.folderFileCount(parentRoot[i], this.directoryTree.parentNameTagsArr()[i], true); + } + + //hide file directory kebab dropdown menu container + (document.getElementById('file-directory-kebab-dropdown-menu-container') as HTMLElement).style.display = "none"; + } else if(isModeAdvanced()) { + fsMod.fs._deletePath( + fsMod.fs._baseDir("home") + + "/Iris/Notes/" + + document.title.split('-')[1].trim() + + "/" + + el.textContent + ".md" + ); + + CMEditorView.editorView.destroy(); + CMEditorView.createEditorView(); + CMEditorView.setContenteditable(false); + + const parentRoot: NodeListOf = document.querySelectorAll('.parent-of-root-folder'); + const parentNameTags: NodeListOf = document.querySelectorAll('.parent-folder-name'); - CMEditorView.editorView.destroy(); - CMEditorView.createEditorView(); - CMEditorView.setContenteditable(false); - - const parentRoot: NodeListOf = document.querySelectorAll('.parent-of-root-folder'); - const parentNameTags: NodeListOf = document.querySelectorAll('.parent-folder-name'); - - let count: number = 0; - //remove duplicate folder file count nodes - while(count <= 2) { - document.querySelectorAll('.folder-file-count-container').forEach((el) => { - el.remove(); - }); - count++; - } - - for(let i = 0; i < parentNameTags.length; i++) { - //invoke folder file count - this.folderFileCount.folderFileCount(parentRoot[i], this.directoryTree.parentNameTagsArr()[i], true); - } + let count: number = 0; + //remove duplicate folder file count nodes + while(count <= 2) { + document.querySelectorAll('.folder-file-count-container').forEach((el) => { + el.remove(); + }); + count++; + } + + for(let i = 0; i < parentNameTags.length; i++) { + //invoke folder file count + this.folderFileCount.folderFileCount(parentRoot[i], this.directoryTree.parentNameTagsArr()[i], true); } + } + + //remove kebab modal container node + EditorKebabDropdownModals.kebabModalContainerNode.remove(); + + //remove active file from tree + el.remove(); + + //hide kebab dropdown menu container + (document.getElementById('kebab-dropdown-menu-container') as HTMLElement).style.display = "none"; + + //kebab after click menu container + (document.getElementById('kebab-after-click-menu-container') as HTMLElement).style.display = "none"; + (document.getElementById('kebab-after-click-menu-container') as HTMLElement).classList.remove('is-active'); + + //word counter + (document.getElementById('word-count-container') as HTMLElement).style.display = "none"; - //remove kebab modal container node - EditorKebabDropdownModals.kebabModalContainerNode.remove(); + //set window title to default + await setWindowTitle("Iris", false, null); - //remove active file from tree - el.remove(); + //remove top bar directory info node + (document.getElementById('top-bar-directory-info') as HTMLElement).remove(); - //hide kebab dropdown menu container - (document.getElementById('kebab-dropdown-menu-container') as HTMLElement).style.display = "none"; + (document.getElementById('file-directory-kebab-dropdown-menu-container') as HTMLElement).style.display = "none"; + }) - //kebab after click menu container - (document.getElementById('kebab-after-click-menu-container') as HTMLElement).style.display = "none"; - (document.getElementById('kebab-after-click-menu-container') as HTMLElement).classList.remove('is-active'); + GenericEvent.use.disposeEvent(window, 'keydown', KeyBinds.map.bindCb, undefined, "Disposed event for kebab delete file continue (keydown enter)"); + } - //word counter - (document.getElementById('word-count-container') as HTMLElement).style.display = "none"; - - //set window title to default - await setWindowTitle("Iris", false, null); + /** + * Kebab delete file continue modal listener + */ + public kebabDeleteFileContinueModalListener(): void { + GenericEvent.use.createDisposableEvent(EditorKebabDropdownModals.kebabModalContinueButtonNode, 'click', this.kebabDeleteFileContinueCb, undefined, "Created event for kebab modal continue button (click)"); - //remove top bar directory info node - (document.getElementById('top-bar-directory-info') as HTMLElement).remove(); + KeyBinds.map.bind(this.kebabDeleteFileContinueCb, "Enter", false); + } - (document.getElementById('file-directory-kebab-dropdown-menu-container') as HTMLElement).style.display = "none"; - }) + public kebabDropdownDeleteFileCb: () => void = (): void => { + //log + console.log("clicked kebab delete"); + + document.querySelectorAll('#kebab-modal-container-node').forEach((el) => { + //null check + if(el !== null) { + //remove any remaining kebab modal container nodes + el.remove(); + } + + GenericEvent.use.disposeEvent((document.getElementById('kebab-delete-file-button-node') as HTMLElement), 'click', this.kebabDropdownDeleteFileCb, undefined, "Disposed event for kebab dropdown delete file (click)") }) + + //create kebab modal container + this.kebabModalDeleteFileContainer(); + + //invoke kebab delete file exit modal listener + this.kebabExitModalListener(); + + //invoke kebab delete file continue modal listener + this.kebabDeleteFileContinueModalListener(); } /** * Kebab dropdown delete file listener */ public kebabDropdownDeleteFileListener(): void { - (document.getElementById('kebab-delete-file-button-node') as HTMLElement).addEventListener('click', () => { + GenericEvent.use.createDisposableEvent((document.getElementById('kebab-delete-file-button-node') as HTMLElement), 'click', this.kebabDropdownDeleteFileCb, undefined, "Created event for kebab dropdown delete file (click)"); + } + + /** + * Kebab rename file continue callback + * + * @public + */ + public kebabRenameFileContinueCb: () => void = (): void => { + console.log(((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0]); + console.log(fsMod.fs._baseDir("home") + "/Iris/Notes" + ((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0].trim() + "/" + (document.querySelector('.child-file-name.is-active-child') as HTMLElement).textContent); + console.log(fsMod.fs._baseDir("home") + "/Iris/Notes" + ((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0].trim() + "/" + this.renameFile); + + if(this.renameFile === " " || this.renameFile === "" || this.renameFile === (document.querySelector('.child-file-name.is-active-child') as HTMLElement).textContent || (document.getElementById('rename-file-input-node') as HTMLElement).textContent === (document.querySelector('.child-file-name.is-active-child') as HTMLElement).textContent) { //log - console.log("clicked kebab delete"); + console.log("name is equal or empty"); - document.querySelectorAll('#kebab-modal-container-node').forEach((el) => { - //null check - if(el !== null) { - //remove any remaining kebab modal container nodes - el.remove(); - } - }) + window.electron.ipcRenderer.invoke('show-message-box', "Cannot rename note. Name must be different and not empty."); - //create kebab modal container - this.kebabModalDeleteFileContainer(); + return; + } else { + //log + console.log("name is not equal or empty"); - //invoke kebab delete file exit modal listener - this.kebabExitModalListener(); + //rename file + fsMod.fs._renameFile( + fsMod.fs._baseDir("home") + "/Iris/Notes/" + ((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0].trim() + "/" + (document.querySelector('.child-file-name.is-active-child') as HTMLElement).textContent + ".md", + fsMod.fs._baseDir("home") + "/Iris/Notes/" + ((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0].trim() + "/" + this.renameFile + ".md" + ) - //invoke kebab delete file continue modal listener - this.kebabDeleteFileContinueModalListener(); - }) + //remove kebab modal container node + EditorKebabDropdownModals.kebabModalContainerNode.remove(); + + //create top bar info + const topBarInfo: string = ((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0].trim() + " - " + this.renameFile; + + //update top bar directory info + (document.getElementById('top-bar-directory-info') as HTMLElement).textContent = topBarInfo; + + //update child file name in directory tree + (document.querySelector('.child-file-name.is-active-child') as HTMLElement).textContent = this.renameFile; + + //update child file name reference + RefsNs.currentParentChildData.map((props) => { + props.childFileName = this.renameFile + }); + + GenericEvent.use.disposeEvent((document.getElementById('kebab-modal-continue-button') as HTMLElement), 'click', this.kebabRenameFileContinueCb, undefined, "Disposed event for kebab rename file continue (click)"); + GenericEvent.use.disposeEvent(window, 'keydown', KeyBinds.map.bindCb, undefined, "Disposed event for kebab rename file continue (keydown enter)"); + } } - public kebabRenameFileContinueModalListener(): void { - let renameFile: string = ""; + public renameFileCb: (e: KeyboardEvent) => void = (e: KeyboardEvent): void => { + this.renameFile = ((e.target as HTMLInputElement).value).trim(); - (document.getElementById('rename-file-input-node') as HTMLElement).addEventListener('keyup', (e) => { - renameFile = ((e.target as HTMLInputElement).value).trim(); + (document.getElementById('rename-file-input-node') as HTMLElement).textContent = this.renameFile; - (document.getElementById('rename-file-input-node') as HTMLElement).textContent = renameFile; + //log + console.log(this.renameFile); + } - //log - console.log(renameFile); - }); + public kebabRenameFileContinueModalListener(): void { + GenericEvent.use.createDisposableEvent((document.getElementById('rename-file-input-node') as HTMLElement), 'keyup', this.renameFileCb, undefined, "Created event for rename file (keyup)"); - (document.getElementById('kebab-modal-continue-button') as HTMLElement).addEventListener('click', () => { - console.log(((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0]); - console.log(fsMod.fs._baseDir("home") + "/Iris/Notes" + ((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0].trim() + "/" + (document.querySelector('.child-file-name.is-active-child') as HTMLElement).textContent); - console.log(fsMod.fs._baseDir("home") + "/Iris/Notes" + ((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0].trim() + "/" + renameFile); - if(renameFile === " " || renameFile === "" || renameFile === (document.querySelector('.child-file-name.is-active-child') as HTMLElement).textContent || (document.getElementById('rename-file-input-node') as HTMLElement).textContent === (document.querySelector('.child-file-name.is-active-child') as HTMLElement).textContent) { - //log - console.log("name is equal or empty"); - - return; - } else { - //log - console.log("name is not equal or empty"); + GenericEvent.use.createDisposableEvent((document.getElementById('kebab-modal-continue-button') as HTMLElement), 'click', this.kebabRenameFileContinueCb, undefined, "Created event for kebab rename file continue (click)"); - //rename file - fsMod.fs._renameFile( - fsMod.fs._baseDir("home") + "/Iris/Notes/" + ((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0].trim() + "/" + (document.querySelector('.child-file-name.is-active-child') as HTMLElement).textContent + ".md", - fsMod.fs._baseDir("home") + "/Iris/Notes/" + ((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0].trim() + "/" + renameFile + ".md" - ) - - //remove kebab modal container node - EditorKebabDropdownModals.kebabModalContainerNode.remove(); - - //create top bar info - const topBarInfo: string = ((document.getElementById("top-bar-directory-info") as HTMLElement).textContent as string).split("-")[0].trim() + " - " + renameFile; - - //update top bar directory info - (document.getElementById('top-bar-directory-info') as HTMLElement).textContent = topBarInfo; - - //update child file name in directory tree - (document.querySelector('.child-file-name.is-active-child') as HTMLElement).textContent = renameFile; - - //update child file name reference - RefsNs.currentParentChildData.map((props) => { - props.childFileName = renameFile - }); + KeyBinds.map.bind(this.kebabRenameFileContinueCb, "Enter", false); + } + + public kebabDropdownRenameFileCb: () => void = (): void => { + //log + console.log("clicked kebab rename"); + + document.querySelectorAll('#kebab-modal-container-node').forEach((el) => { + //null check + if(el !== null) { + //remove any remaining kebab modal container nodes + el.remove(); } }) + + GenericEvent.use.setEventCallbackTimeout(() => { + this.kebabDropdownRenameFileContainer(); + + this.kebabExitModalListener(); + + this.kebabRenameFileContinueModalListener(); + }, 20); } /** * Kebab dropdown rename file listener */ public kebabDropdownRenameFileListener(): void { - (document.getElementById('kebab-rename-file-button') as HTMLElement).addEventListener('click', () => { - //log - console.log("clicked kebab rename"); - - document.querySelectorAll('#kebab-modal-container-node').forEach((el) => { - //null check - if(el !== null) { - //remove any remaining kebab modal container nodes - el.remove(); - } - }) - - this.kebabDropdownRenameFileContainer(); - - this.kebabExitModalListener(); - - this.kebabRenameFileContinueModalListener(); - }) + GenericEvent.use.createDisposableEvent((document.getElementById('kebab-rename-file-button') as HTMLElement), 'click', () => GenericEvent.use.setEventCallbackTimeout(this.kebabDropdownRenameFileCb, 50), undefined, "Created disposable event for kebab dropdown rename file"); } /** diff --git a/src/renderer/src/event-listeners/settings-modal-listeners.ts b/src/renderer/src/event-listeners/settings-modal-listeners.ts index 1e7b494..9832648 100644 --- a/src/renderer/src/event-listeners/settings-modal-listeners.ts +++ b/src/renderer/src/event-listeners/settings-modal-listeners.ts @@ -5,6 +5,9 @@ import { CMEditorView } from "../codemirror/editor/cm-editor-view" import { CMEditorState } from "../codemirror/editor/cm-editor-state" import { cursors } from "../codemirror/extensions/cursors" import { AdvancedModeSettings } from "../settings/settings" +import { GenericEvent } from "./event" +import { KeyBinds } from "../keybinds/keybinds" + import highlightLight from '../../assets/classic-light.min.css?inline?url' /** @@ -12,12 +15,185 @@ import highlightLight from '../../assets/classic-light.min.css?inline?url' */ export class SettingsModalListeners extends SettingsModal { /** - * Settings modal exit listener + * Theme select callback + * + * @public */ - public settingsModalExitListener(): void { - SettingsModal.settingsModalExitButton.addEventListener('click', () => { - SettingsModal.settingsModalContainerNode.remove(); - }) + public themeSelectCb: (e: Event) => void = (e: Event): void => { + const currentSelection = (e.currentTarget as HTMLSelectElement); + + //if dark theme exists in dom + if((document.querySelector('.editor-dark-theme') as HTMLElement) !== null) { + //remove stylesheet node + document.querySelectorAll('.editor-dark-theme').forEach((el) => { + el.remove(); + }) + } + + //if selection is light theme + if(currentSelection.value === 'editor-light') { + //log + console.log("selected editor light"); + + (document.querySelector('.dark-option') as HTMLElement).removeAttribute("selected"); + + (document.querySelector('.light-option') as HTMLElement).setAttribute("selected", ""); + + //check highlight light + if((document.querySelector('.highlight-light-theme') as HTMLElement) !== null) { + document.querySelectorAll('.highlight-light-theme').forEach((el) => { + el.remove(); + }) + } + + //check highlight dark + if((document.querySelector('.highlight-dark-theme') as HTMLElement) !== null) { + document.querySelectorAll('.highlight-dark-theme').forEach((el) => { + el.remove(); + }) + } + + const highlightTheme: HTMLLinkElement = document.createElement('link'); + highlightTheme.setAttribute("rel", "stylesheet"); + highlightTheme.setAttribute("href", highlightLight); + highlightTheme.setAttribute("class", "highlight-light-theme"); + document.body.appendChild(highlightTheme); + + Settings.getSettings.lightTheme = true; + Settings.getSettings.darkTheme = false; + + if(!Settings.getSettings.lightTheme) { + fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); + } else { + fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); + } + + //check mode + if(Settings.getSettings.basicMode) { + CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[0]) }) + } else if(Settings.getSettings.advancedMode) { + CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[0]) }) + } + + //check block cursor + if(Settings.getSettings.defaultCursor && Settings.getSettings.lightTheme) { + AdvancedModeSettings.defaultCursor("light"); + } else if(Settings.getSettings.defaultCursor && Settings.getSettings.darkTheme) { + AdvancedModeSettings.defaultCursor("dark"); + } else if(Settings.getSettings.blockCursor && Settings.getSettings.lightTheme || Settings.getSettings.blockCursor && Settings.getSettings.darkTheme) { + AdvancedModeSettings.blockCursor(); + } + //if selection is dark theme + } else if(currentSelection.value === 'editor-dark') { + //log + console.log("selected editor dark"); + + (document.querySelector('.light-option') as HTMLElement).removeAttribute("selected"); + + (document.querySelector('.dark-option') as HTMLElement).setAttribute("selected", ""); + + //check highlight light + if((document.querySelector('.highlight-light-theme') as HTMLElement) !== null) { + document.querySelectorAll('.highlight-light-theme').forEach((el) => { + el.remove(); + }) + } + + //check highlight dark + if((document.querySelector('.highlight-dark-theme') as HTMLElement) !== null) { + document.querySelectorAll('.highlight-dark-theme').forEach((el) => { + el.remove(); + }) + } + + //apply dark theme + EditorThemes.darkTheme(); + + Settings.getSettings.lightTheme = false; + Settings.getSettings.darkTheme = true; + + if(!Settings.getSettings.darkTheme) { + fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); + } else { + fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); + } + + //check mode + if(Settings.getSettings.basicMode) { + CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[1]) }) + } else if(Settings.getSettings.advancedMode) { + CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[1]) }) + } + + //check block cursor + if(Settings.getSettings.defaultCursor && Settings.getSettings.lightTheme) { + AdvancedModeSettings.defaultCursor("light"); + } else if(Settings.getSettings.defaultCursor && Settings.getSettings.darkTheme) { + AdvancedModeSettings.defaultCursor("dark"); + } else if(Settings.getSettings.blockCursor && Settings.getSettings.lightTheme || Settings.getSettings.darkTheme && Settings.getSettings.darkTheme) { + AdvancedModeSettings.blockCursor(); + } + } + } + + /** + * Cursor settings callback + * + * @public + */ + public cursorSettingsCb: (e: Event) => void = (e: Event): void => { + const currentSelection: HTMLSelectElement = (e.currentTarget as HTMLSelectElement); + + //if current selection is default-cursor and theme is light + if(currentSelection.value === 'default-cursor' && Settings.getSettings.lightTheme) { + //log + console.log("selected default cursor and theme is light"); + + (document.querySelector('.block-cursor-option') as HTMLElement).removeAttribute("selected"); + (document.querySelector('.default-cursor-option') as HTMLElement).setAttribute("selected", ""); + + Settings.getSettings.defaultCursor = true; + Settings.getSettings.blockCursor = false; + + fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); + + //dispatch cursor compartment reconfiguration + CMEditorView.editorView.dispatch({ + effects: CMEditorState.cursorCompartment.reconfigure(cursors[0]) + }) + //if current selection is default-cursor and theme is dark + } else if(currentSelection.value === 'default-cursor' && Settings.getSettings.darkTheme) { + //log + console.log("selected default cursor and theme is dark"); + + (document.querySelector('.block-cursor-option') as HTMLElement).removeAttribute("selected"); + (document.querySelector('.default-cursor-option') as HTMLElement).setAttribute("selected", ""); + + Settings.getSettings.defaultCursor = true; + Settings.getSettings.blockCursor = false; + + fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); + + CMEditorView.editorView.dispatch({ + effects: CMEditorState.cursorCompartment.reconfigure(cursors[1]) + }) + //if current selection is block-cursor + } else if(currentSelection.value === 'block-cursor') { + //log + console.log("selected block cursor"); + + (document.querySelector('.default-cursor-option') as HTMLElement).removeAttribute("selected"); + (document.querySelector('.block-cursor-option') as HTMLElement).setAttribute("selected", ""); + + Settings.getSettings.defaultCursor = false; + Settings.getSettings.blockCursor = true; + + fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); + + CMEditorView.editorView.dispatch({ + effects: CMEditorState.cursorCompartment.reconfigure(cursors[2]) + }) + } } /** @@ -34,128 +210,7 @@ export class SettingsModalListeners extends SettingsModal { return; } else { - (document.getElementById("theme-select") as HTMLElement).addEventListener('change', (e) => { - const currentSelection = (e.currentTarget as HTMLSelectElement); - - //if dark theme exists in dom - if((document.querySelector('.editor-dark-theme') as HTMLElement) !== null) { - //remove stylesheet node - document.querySelectorAll('.editor-dark-theme').forEach((el) => { - el.remove(); - }) - } - - //if selection is light theme - if(currentSelection.value === 'editor-light') { - //log - console.log("selected editor light"); - - (document.querySelector('.dark-option') as HTMLElement).removeAttribute("selected"); - - (document.querySelector('.light-option') as HTMLElement).setAttribute("selected", ""); - - //check highlight light - if((document.querySelector('.highlight-light-theme') as HTMLElement) !== null) { - document.querySelectorAll('.highlight-light-theme').forEach((el) => { - el.remove(); - }) - } - - //check highlight dark - if((document.querySelector('.highlight-dark-theme') as HTMLElement) !== null) { - document.querySelectorAll('.highlight-dark-theme').forEach((el) => { - el.remove(); - }) - } - - const highlightTheme: HTMLLinkElement = document.createElement('link'); - highlightTheme.setAttribute("rel", "stylesheet"); - highlightTheme.setAttribute("href", highlightLight); - highlightTheme.setAttribute("class", "highlight-light-theme"); - document.body.appendChild(highlightTheme); - - Settings.getSettings.lightTheme = true; - Settings.getSettings.darkTheme = false; - - if(!Settings.getSettings.lightTheme) { - fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); - } else { - fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); - } - - //check mode - if(Settings.getSettings.basicMode) { - CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[0]) }) - } else if(Settings.getSettings.advancedMode) { - CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[0]) }) - } - - //check block cursor - if(Settings.getSettings.defaultCursor && Settings.getSettings.lightTheme) { - AdvancedModeSettings.defaultCursor("light"); - } else if(Settings.getSettings.defaultCursor && Settings.getSettings.darkTheme) { - AdvancedModeSettings.defaultCursor("dark"); - } else if( - Settings.getSettings.blockCursor && Settings.getSettings.lightTheme - || Settings.getSettings.blockCursor && Settings.getSettings.darkTheme - ) { - AdvancedModeSettings.blockCursor(); - } - //if selection is dark theme - } else if(currentSelection.value === 'editor-dark') { - //log - console.log("selected editor dark"); - - (document.querySelector('.light-option') as HTMLElement).removeAttribute("selected"); - - (document.querySelector('.dark-option') as HTMLElement).setAttribute("selected", ""); - - //check highlight light - if((document.querySelector('.highlight-light-theme') as HTMLElement) !== null) { - document.querySelectorAll('.highlight-light-theme').forEach((el) => { - el.remove(); - }) - } - - //check highlight dark - if((document.querySelector('.highlight-dark-theme') as HTMLElement) !== null) { - document.querySelectorAll('.highlight-dark-theme').forEach((el) => { - el.remove(); - }) - } - - //apply dark theme - EditorThemes.darkTheme(); - - Settings.getSettings.lightTheme = false; - Settings.getSettings.darkTheme = true; - - if(!Settings.getSettings.darkTheme) { - fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); - } else { - fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); - } - - //check mode - if(Settings.getSettings.basicMode) { - CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[1]) }) - } else if(Settings.getSettings.advancedMode) { - CMEditorView.editorView.dispatch({ effects: CMEditorState.cursorCompartment.reconfigure(cursors[1]) }) - } - - //check block cursor - if(Settings.getSettings.defaultCursor && Settings.getSettings.lightTheme) { - AdvancedModeSettings.defaultCursor("light"); - } else if(Settings.getSettings.defaultCursor && Settings.getSettings.darkTheme) { - AdvancedModeSettings.defaultCursor("dark"); - } else if( - Settings.getSettings.blockCursor && Settings.getSettings.lightTheme - || Settings.getSettings.darkTheme && Settings.getSettings.darkTheme - ) { - AdvancedModeSettings.blockCursor(); - } - } - }) + GenericEvent.use.createDisposableEvent((document.getElementById("theme-select") as HTMLElement), 'change', this.themeSelectCb, undefined, "Create disposable event for theme select (change)"); } } @@ -165,80 +220,77 @@ export class SettingsModalListeners extends SettingsModal { * @private */ private cursorSettingsListener(): void { - (document.getElementById('advanced-mode-options-select') as HTMLElement).addEventListener('change', (e) => { - const currentSelection: HTMLSelectElement = (e.currentTarget as HTMLSelectElement); - - //if current selection is default-cursor and theme is light - if(currentSelection.value === 'default-cursor' && Settings.getSettings.lightTheme) { - //log - console.log("selected default cursor and theme is light"); - - (document.querySelector('.block-cursor-option') as HTMLElement).removeAttribute("selected"); - (document.querySelector('.default-cursor-option') as HTMLElement).setAttribute("selected", ""); - - Settings.getSettings.defaultCursor = true; - Settings.getSettings.blockCursor = false; - - fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); - - //dispatch cursor compartment reconfiguration - CMEditorView.editorView.dispatch({ - effects: CMEditorState.cursorCompartment.reconfigure(cursors[0]) - }) - //if current selection is default-cursor and theme is dark - } else if(currentSelection.value === 'default-cursor' && Settings.getSettings.darkTheme) { - //log - console.log("selected default cursor and theme is dark"); + GenericEvent.use.createDisposableEvent((document.getElementById('advanced-mode-options-select') as HTMLElement), 'change', this.cursorSettingsCb, undefined, "Created disposable event for cursor settings (change)"); + } - (document.querySelector('.block-cursor-option') as HTMLElement).removeAttribute("selected"); - (document.querySelector('.default-cursor-option') as HTMLElement).setAttribute("selected", ""); + /** + * Settings modal exit callback + * + * @public + */ + public settingsModalExitCb: () => void = (): void => { + SettingsModal.settingsModalContainerNode.remove(); - Settings.getSettings.defaultCursor = true; - Settings.getSettings.blockCursor = false; + GenericEvent.use.setEventCallbackTimeout(() => { + //dispose theme select cb + GenericEvent.use.disposeEvent(document.body, 'change', this.themeSelectCb, undefined, "Disposed event for theme select (change)"); - fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); + //dispose cursor settings cb + GenericEvent.use.disposeEvent(document.body, 'change', this.cursorSettingsCb, undefined, "Disposed event for cursor settings (change)"); + + //dispose settings modal exit cb + GenericEvent.use.disposeEvent(SettingsModal.settingsModalExitButton, 'click', this.settingsModalExitCb, undefined, "Disposed event for settings modal exit (click)"); - CMEditorView.editorView.dispatch({ - effects: CMEditorState.cursorCompartment.reconfigure(cursors[1]) - }) - //if current selection is block-cursor - } else if(currentSelection.value === 'block-cursor') { - //log - console.log("selected block cursor"); + //dispose bind cb + GenericEvent.use.disposeEvent(window, 'keydown', KeyBinds.map.bindCb, undefined, "Disposed event for bind (keydown escape)"); + }, 150) - (document.querySelector('.default-cursor-option') as HTMLElement).removeAttribute("selected"); - (document.querySelector('.block-cursor-option') as HTMLElement).setAttribute("selected", ""); + //reset map list + KeyBinds.map.resetMapList(); + } - Settings.getSettings.defaultCursor = false; - Settings.getSettings.blockCursor = true; + /** + * Settings modal exit listener + */ + public settingsModalExitListener(): void { + KeyBinds.map.bind(this.settingsModalExitCb, 'Escape', true); - fsMod.fs._writeToFileAlt(fsMod.fs._baseDir("home") + "/Iris/.settings.json", JSON.stringify(JSON.parse(JSON.stringify(Settings.getSettings, null, 2)), null, 2)); + GenericEvent.use.createDisposableEvent(SettingsModal.settingsModalExitButton, 'click', this.settingsModalExitCb, undefined, "Created disposable event for settings modal exit (click)"); + } - CMEditorView.editorView.dispatch({ - effects: CMEditorState.cursorCompartment.reconfigure(cursors[2]) - }) - } - }) + /** + * Settings modal callback + * + * @public + */ + public settingsModalCb: () => void = (): void => { + console.log("clicked settings node"); + + GenericEvent.use.setEventCallbackTimeout( + () => { + //create settings modal container + this.settingsModalContainer(); + + //theme settings listener + this.themeSettingsListener(); + + //cursor settings listener + this.cursorSettingsListener(); + + //invoke settings modal exit listener + this.settingsModalExitListener(); + + //dispose settings modal cb + GenericEvent.use.disposeEvent((document.getElementById("settings-node") as HTMLElement), 'click', () => GenericEvent.use.setEventCallbackTimeout(this.settingsModalCb, 50), undefined, "Disposed event for settings modal (click)"); + }, 50 + ) } + /** * Settings modal listener */ public settingsModalListener(): void { - (document.getElementById("settings-node") as HTMLElement).addEventListener('click', () => { - console.log("clicked settings node"); - - //create settings modal container - this.settingsModalContainer(); - - //invoke settings modal exit listener - this.settingsModalExitListener(); - - //theme settings listener - this.themeSettingsListener(); - - //cursor settings listener - this.cursorSettingsListener(); - }) + GenericEvent.use.createDisposableEvent((document.getElementById("settings-node") as HTMLElement), 'click', () => GenericEvent.use.setEventCallbackTimeout(this.settingsModalCb, 50), undefined, "Create disposable event for settings modal (click)"); } } \ No newline at end of file diff --git a/src/renderer/src/file-directory-tree/file-directory.ts b/src/renderer/src/file-directory-tree/file-directory.ts index 79c08ab..81e9739 100644 --- a/src/renderer/src/file-directory-tree/file-directory.ts +++ b/src/renderer/src/file-directory-tree/file-directory.ts @@ -3,7 +3,6 @@ import { fsMod } from '../utils/alias' import { isFolderNode } from '../utils/is' import { Settings } from '../settings/settings' -//eslint-disable-next-line @typescript-eslint/no-namespace export class FileDirectoryTreeNode { /** * File directory tree node diff --git a/src/renderer/src/keybinds/constants.ts b/src/renderer/src/keybinds/constants.ts new file mode 100644 index 0000000..7619fd9 --- /dev/null +++ b/src/renderer/src/keybinds/constants.ts @@ -0,0 +1,4 @@ +export enum EKeyMap { + ESCAPE = 'Escape', + ENTER = 'Enter' +} diff --git a/src/renderer/src/keybinds/keybinds.ts b/src/renderer/src/keybinds/keybinds.ts new file mode 100644 index 0000000..781748a --- /dev/null +++ b/src/renderer/src/keybinds/keybinds.ts @@ -0,0 +1,157 @@ +import { EKeyMap } from "./constants" +import { GenericEvent } from "../event-listeners/event" +import { GenericArray } from "../utils/array" + +interface IFn { + fn: (...args: any[]) => any, + key: string +} + +export namespace KeyBinds { + class KeyMapper { + /** + * Array of objects to hold key binds and their corresponding functions + * + * @private + */ + private map: IFn[] = [ + { + fn: {} as (...args: any[]) => any, + key: "" + }, + { + fn: {} as (...args: any[]) => any, + key: "" + } + ]; + + /** + * Reset map list + * + * @public + */ + public resetMapList(): void { + GenericEvent.use.setEventCallbackTimeout(() => { + this.map = [ + { + fn: {} as (...args: any[]) => any, + key: "" + }, + { + fn: {} as (...args: any[]) => any, + key: "" + } + ]; + + console.log("Reset map"); + }, 200); + } + + /** + * Set bind + * + * @param key Type of key to execute corresponding function (`Escape`, `Enter`) + * + * @public + */ + public setBind(key: string): void { + console.log(this.map.length); + + if(key === 'Escape') { + GenericEvent.use.setEventCallbackTimeout(this.map[0].fn, 20); + } else if(key === 'Enter') { + GenericEvent.use.setEventCallbackTimeout(this.map[1].fn, 20); + } + } + + /** + * Bind callback + * + * @param e KeyboardEvent + * + * @public + */ + public bindCb: (e: KeyboardEvent) => any = (e: KeyboardEvent): any => { + console.log(this.map); + console.log(this.map.length); + + switch(e.code) { + case EKeyMap.ESCAPE: { + if(this.map[0].key === EKeyMap.ESCAPE) { + console.log("Escape bind"); + + this.setBind("Escape"); + } + break; + } + case EKeyMap.ENTER: { + if(this.map[1].key === EKeyMap.ENTER) { + console.log("Enter bind"); + + this.setBind("Enter"); + } + break; + } + } + } + + /** + * Remap binding for single key + * + * @param fn Function + * @param key Key type + * @returns Remapped binding for single key + * + * @private + */ + private remapSingle(fn: (...args: any[]) => any, key: string): IFn[] { + //empty keybind map before reassigning value to this.map + GenericArray.use.empty(this.map, false); + + return this.map = [ + { + fn: fn, + key: key + } + ] + } + + /** + * Bind a key + * + * @param fn Function + * @param key Key type (`Escape`, `Enter`) + * @param singleKey Option for binding a single key only + */ + public bind(fn: (...args: any[]) => any, key: string, singleKey: boolean): void { + if(singleKey) { + if(key === EKeyMap.ESCAPE) { + this.remapSingle(fn, key); + } else if(key === EKeyMap.ENTER) { + this.remapSingle(fn, key) + } + + GenericEvent.use.createDisposableEvent(window, 'keydown', this.bindCb, undefined, "Created disposable event for bind (keydown)"); + + return; + } else if(!singleKey) { + if(key === 'Escape') { + this.map[0].fn = fn; + this.map[0].key = key + } else if(key === 'Enter') { + this.map[1].fn = fn; + this.map[1].key = key; + } + + GenericEvent.use.createDisposableEvent(window, 'keydown', this.bindCb, undefined, "Created disposable event for bind (keydown)"); + + return; + } + } + } + + /** + * KeyMapper object + */ + export const map: KeyMapper = new KeyMapper(); +} \ No newline at end of file diff --git a/src/renderer/src/renderer.ts b/src/renderer/src/renderer.ts index 1b32c50..1369011 100644 --- a/src/renderer/src/renderer.ts +++ b/src/renderer/src/renderer.ts @@ -4,96 +4,134 @@ import { windowNs } from './window/draggable-area' import { Settings, EditorThemes } from '../src/settings/settings' import { AdvancedModeSettings } from '../src/settings/settings' import mermaid from 'mermaid' - -export function initRenderer(): void { - window.addEventListener('DOMContentLoaded', () => { - //draggable area - windowNs.draggableArea(); - - //directory - directoryNs.directory(); - - //editor - EditorNs.editor(); - - //load themes - if(Settings.getSettings.lightTheme) { - //if dark theme exists in dom - if((document.querySelector('.editor-dark-theme') as HTMLElement) !== null) { - //remove stylesheet node - document.querySelectorAll('.editor-dark-theme').forEach((el) => { - el.remove(); - }) - - document.querySelectorAll('.highlight-dark-theme').forEach((el) => { - el.remove(); - }) - } - - //initialize mermaid - mermaid.initialize({ - theme: 'forest' - }) - //dark theme - } else if(Settings.getSettings.darkTheme) { - EditorThemes.darkTheme(); - } - - if(Settings.getSettings.darkTheme) { - document.querySelectorAll('.highlight-light-theme').forEach((el) => { +import { GenericEvent } from './event-listeners/event' + +namespace RendererProcess { + class Renderer { + /** + * Coupled namespace function + * + * @private + */ + private coupleNs(): void { + //draggable area + windowNs.draggableArea(); + + //directory + directoryNs.directory(); + + //editor + EditorNs.editor(); + } + + /** + * Load theme + * + * @private + */ + private loadTheme(): void { + //load themes + if(Settings.getSettings.lightTheme) { + //if dark theme exists in dom + if((document.querySelector('.editor-dark-theme') as HTMLElement) !== null) { + //remove stylesheet node + document.querySelectorAll('.editor-dark-theme').forEach((el) => { el.remove(); }) - //initialize mermaid - mermaid.initialize({ - theme: 'forest' + document.querySelectorAll('.highlight-dark-theme').forEach((el) => { + el.remove(); }) } + //dark theme + } else if(Settings.getSettings.darkTheme) { + EditorThemes.darkTheme(); + + document.querySelectorAll('.highlight-light-theme').forEach((el) => { + el.remove(); + }) + } + + //initialize mermaid + mermaid.initialize({ + theme: 'forest', + startOnLoad: true + }) + } + + /** + * Load mode + * + * @private + */ + private loadMode(): void { + //load mode + if(Settings.getSettings.basicMode) { + console.log("basic mode active"); + + if((document.getElementById('app') as HTMLElement).classList.contains('advanced-mode-is-active')) { + (document.getElementById('app') as HTMLElement).classList.remove('advanced-mode-is-active'); + } + + (document.getElementById('app') as HTMLElement).classList.add('basic-mode-is-active'); + //advanced mode + } else if(Settings.getSettings.advancedMode) { + console.log("advanced mode active"); + + if((document.getElementById('app') as HTMLElement).classList.contains('basic-mode-is-active')) { + (document.getElementById('app') as HTMLElement).classList.remove('basic-mode-is-active'); + } + + (document.getElementById('app') as HTMLElement).classList.add('advanced-mode-is-active'); + + //check block cursor + if(Settings.getSettings.defaultCursor && Settings.getSettings.lightTheme) { + AdvancedModeSettings.defaultCursor("light"); + } else if(Settings.getSettings.defaultCursor && Settings.getSettings.darkTheme) { + AdvancedModeSettings.defaultCursor("dark"); + } else if(Settings.getSettings.blockCursor && Settings.getSettings.lightTheme || Settings.getSettings.blockCursor && Settings.getSettings.darkTheme) { + AdvancedModeSettings.blockCursor(); + } + } else if(Settings.getSettings.readingMode) { + console.log("reading mode active"); - //load mode - if(Settings.getSettings.basicMode) { - console.log("basic mode active"); - - if((document.getElementById('app') as HTMLElement).classList.contains('advanced-mode-is-active')) { - (document.getElementById('app') as HTMLElement).classList.remove('advanced-mode-is-active'); - } - - (document.getElementById('app') as HTMLElement).classList.add('basic-mode-is-active'); - //advanced mode - } else if(Settings.getSettings.advancedMode) { - console.log("advanced mode active"); - - if((document.getElementById('app') as HTMLElement).classList.contains('basic-mode-is-active')) { - (document.getElementById('app') as HTMLElement).classList.remove('basic-mode-is-active'); - } - - (document.getElementById('app') as HTMLElement).classList.add('advanced-mode-is-active'); - - //check block cursor - if(Settings.getSettings.defaultCursor && Settings.getSettings.lightTheme) { - AdvancedModeSettings.defaultCursor("light"); - } else if(Settings.getSettings.defaultCursor && Settings.getSettings.darkTheme) { - AdvancedModeSettings.defaultCursor("dark"); - } else if( - Settings.getSettings.blockCursor && Settings.getSettings.lightTheme - || Settings.getSettings.blockCursor && Settings.getSettings.darkTheme - ) { - AdvancedModeSettings.blockCursor(); - } - } else if(Settings.getSettings.readingMode) { - console.log("reading mode active"); - - if((document.getElementById('app') as HTMLElement).classList.contains('basic-mode-is-active')) { - (document.getElementById('app') as HTMLElement).classList.remove('basic-mode-is-active'); - } else if((document.getElementById('app') as HTMLElement).classList.contains('advanced-mode-is-active')) { - (document.getElementById('app') as HTMLElement).classList.remove('advanced-mode-is-active'); - } - - (document.getElementById('app') as HTMLElement).classList.add('reading-mode-is-active'); + if((document.getElementById('app') as HTMLElement).classList.contains('basic-mode-is-active')) { + (document.getElementById('app') as HTMLElement).classList.remove('basic-mode-is-active'); + } else if((document.getElementById('app') as HTMLElement).classList.contains('advanced-mode-is-active')) { + (document.getElementById('app') as HTMLElement).classList.remove('advanced-mode-is-active'); } - }); + + (document.getElementById('app') as HTMLElement).classList.add('reading-mode-is-active'); + } + } + + /** + * Attach window event + * + * @private + */ + private attachWindowEvent(): void { + GenericEvent.use.createDisposableEvent(window, 'DOMContentLoaded', () => { + this.coupleNs(); + this.loadTheme(); + this.loadMode(); + }) + } + + /** + * Initialize renderer + * + * @public + */ + public initializeRenderer(): void { + this.attachWindowEvent(); + } + } + + /** + * Renderer object + */ + export const execute: Renderer = new Renderer(); } -initRenderer(); -//log test -console.log(window.fsMod._getDirectoryName("/Users/alex/Iris")); \ No newline at end of file +RendererProcess.execute.initializeRenderer(); diff --git a/src/renderer/src/settings/settings-modal.ts b/src/renderer/src/settings/settings-modal.ts index 43e300e..48a3704 100644 --- a/src/renderer/src/settings/settings-modal.ts +++ b/src/renderer/src/settings/settings-modal.ts @@ -16,7 +16,7 @@ export class SettingsModal { SettingsModal.settingsModalExitButton.setAttribute("id", "settings-modal-exit"); SettingsModal.settingsModalInnerWindow.appendChild(SettingsModal.settingsModalExitButton); - const settingsModalExitTextNode: Text = document.createTextNode("Exit"); + const settingsModalExitTextNode: Text = document.createTextNode("x"); SettingsModal.settingsModalExitButton.appendChild(settingsModalExitTextNode); } diff --git a/src/renderer/src/settings/settings.ts b/src/renderer/src/settings/settings.ts index 81c8055..0fbac50 100644 --- a/src/renderer/src/settings/settings.ts +++ b/src/renderer/src/settings/settings.ts @@ -23,6 +23,8 @@ interface ISettingsData { "blockCursor": T } +type TSettings = ISettingsData; + export class Settings { /** * Get local settings @@ -30,7 +32,7 @@ export class Settings { * @static * @readonly */ - static readonly getSettings: ISettingsData = JSON.parse(fsMod.fs._readFile(fsMod.fs._baseDir("home") + "/Iris/.settings.json")) + static readonly getSettings: TSettings = JSON.parse(fsMod.fs._readFile(fsMod.fs._baseDir("home") + "/Iris/.settings.json")) } export class EditorThemes { diff --git a/src/renderer/src/utils/alias.ts b/src/renderer/src/utils/alias.ts index 8a4338d..a13230e 100644 --- a/src/renderer/src/utils/alias.ts +++ b/src/renderer/src/utils/alias.ts @@ -1,4 +1,3 @@ -//eslint-disable-next-line @typescript-eslint/no-namespace export namespace fsMod { /** * fsMod API alias diff --git a/src/renderer/src/utils/array.ts b/src/renderer/src/utils/array.ts new file mode 100644 index 0000000..e70da57 --- /dev/null +++ b/src/renderer/src/utils/array.ts @@ -0,0 +1,33 @@ +interface IArray { + empty(arr: T, pop: K): V +} + +export namespace GenericArray { + /** + * @internal + */ + class Array implements IArray { + /** + * + * @param arr Array to empty + * @param pop Option to empty array by popping the stack instead of setting length + * @returns Emptied array + */ + public empty(arr: T[], pop: K): V { + let empty: number | any[] = {} as number | any[]; + + if(!pop && (arr.length !== 0 && arr.length !== -1 || arr.length !== undefined || arr.length !== null)) { + empty = (arr.length = 0 as number); + } else if(pop) { + while(arr.length > 0) { + empty = arr.pop() as any[]; + } + } + + return empty as V; + } + } + + //eslint-disable-next-line + export const use: Array = new Array(); +} \ No newline at end of file