alexwkleung / iris Goto Github PK
View Code? Open in Web Editor NEW✨ A comfortable note-taking app powered by Markdown
Home Page: https://irisnotes.vercel.app/
License: MIT License
✨ A comfortable note-taking app powered by Markdown
Home Page: https://irisnotes.vercel.app/
License: MIT License
Creating a new folder with a duplicate name (including empty names) causes the app to blank out (crash). On the Rust side, it throws an error if the directory name exists, which is the default behaviour of std::fs::create_dir()
.
thread '<unnamed>' panicked at 'Unable to create directory File exists (os error 17) ', src/create_dir.rs:11:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
fatal runtime error: failed to initiate panic, error 5
It should be correctly handled for better UX. For example, on the front-end side, a pop-up modal should appear when it throws the error.
I decided to go with the debounce implementation from https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_debounce. See (04b282a, 57e0cad, 04dc13f).
I modified it a little so it'll work with TypeScript.
export function debounce(fn: () => any, wait: number | undefined, immediate?: boolean): () => any {
let timeout: string | number | NodeJS.Timeout | undefined;
return function(this: unknown, ...args: []) {
clearTimeout(timeout as string | number | NodeJS.Timeout | undefined);
timeout = setTimeout(() => {
timeout = undefined;
if(!immediate) {
fn.apply(this, args);
}
//added range guard here
}, (wait as number) >= 0 && (wait as number) <= Infinity ? wait : undefined);
if(immediate && !timeout) {
fn.apply(this, args);
}
};
}
Example usage:
import { debounce } from './debounce'
document.body.addEventListener('click', debounce(() => {
console.log("Executed!");
}, 1000))
Unless absolutely necessary for performance or stability reasons, I'll switch back to lodash.
529ba1e adds inserting tab-like characters (spaces) when pressing the tab key in the editor. However, these spaces are included in the word count, causing the calculation to be off.
The regular expression pattern should be modified to handle the tab-like spaces since they are currently being treated as a word.
Improving the file directory should make Iris feel better and more fulfilling to use.
Depending on improvements, there may be drastic changes.
My suggestions:
or
Either one of these options will improve how users organize their notes. Supplying users with an infinite file directory tree allows the ease of importing their Markdown notes without destroying the structure and keeps migrated notes intact.
The differentiating factor between the two suggestions is the UI, not the logic behind it. Both will have the same underlying logic regardless of how it's presented in the front-end.
These suggestions eradicate the limitations of a fixed directory structure in the current implementation.
I hope to work on this in a future release.
When switching between modes, the word count is not consistent. Due to the methods how I handle the word count between both modes, it causes a rather small or large discrepancy. Once you realize this discrepancy, it's hard to tell which one is correct. From the user perspective this is extremely confusing and can be a complaint from those that want pinpoint accuracy.
Furthermore, document lines of CodeMirror and ProseMirror are clearly distinct and don't follow the same tree-like structure, therefore iteration inconsistencies are bound to happen. ProseMirror uses a plain textarea
element (default DOM tree) while CodeMirror builds a complex tree-like structure on top of the textarea
element, causing standard textarea
methods to fail. So proper iteration of the CodeMirror document must be done via a TextIterator
or EditorView.state.doc.iter()
, which can end up with different results compared to the textarea
tree used by ProseMirror.
Instead of the front-end doing the computational work, I was thinking of moving the integer count to the Rust side. The method to extract the word count will be based on the raw file itself and this leads to a more uniform count without relying on inconsistent tree computation. Things like continuously reading a file on every key stroke (i.e., at 0.5ms debounce) may backfire if it isn't optimized correctly but in theory it should be smoother than offloading the iteration to the front-end.
To simulate isolation between modes, add settings to allow the user to disable modes they don't need from the kebab dropdown.
This would most likely satisfy my previous thought of "isolation between the modes", although not 1:1. It would allow the app to conform to what the users prefer and having more flexibility in the settings is a good perk.
When you insert >= 4 spaces in the editor before the first word of a line, it turns the line into a pre tag when you re-open the file. However, inserting >= 4 spaces after the first word is fine and doesn't cause any issues.
This behaviour is by default in the prosemirror-markdown
package, since I can reproduce it in the prosemirror markdown example.
I'll change the insert tab default to 2 spaces instead of 4, but note that the issue will still persist.
Iris is proposed to have basic and advanced modes. The names are placeholders for now.
Roughly speaking, here is a small list of what the basic and advanced modes might contain.
Basic:
Advanced:
The modes cater towards two different types of users and/or two different use cases.
I want to do a major source code clean up and refactoring when time permits. At the moment, the source code is very fragile and quite a mess due to the hack-y stuff I've done. While I surprisingly ended up with something usable, I realized the limitations and problems when moving towards future development of the app.
On the one hand, the back-end seems somewhat robust, so it's just mostly tweaking how I handle panics on the Rust side to be used in the front-end (refer to #23). Of course, system-level panics aren't always the option because an app crash may cause the user to lose data, and that's not ideal nor a good UX when there's no visible warning within the app. I might be missing a few things here, but overall the system-level stuff can be improved if needed.
On the other hand, the front-end is what I am most concerned about. This is where everything is tied in place and is really what makes or break the app. While some of the smaller portions of code are fine, the main logic is where I cut a lot of corners. You might think that the app feels pretty good at its current stage and I do agree with that. However behind that is a source code nightmare and it's simply not feasible to add more features with what I have right now. Thus, a major source code clean up and refactoring must be done before proceeding further. The front-end of Iris is extremely crucial to the user experience, so by fixing the core, the app should feel more robust and rigid, along with the source code being more flexible.
For example, event listeners are not disposed at all within the app's lifetime, so memory issues are a problem from my testing. The implementation to add the dispose event is seen in #20. Not only memory issues, but conflicting event listeners (i.e., mouse or keyboard related) can break the user experience and cause headaches in the source code. At the time I didn't know how to handle multiple event listeners the proper way (when it's necessary) and it forced me to create a bunch of hacks since it was my first time dealing with it.
As stated previously, this will happen when time permits. I am not sure when exactly, but I do want to be clear here. No new features will be added until this is completed. There may be a few minor updates prior, but it won't be related to the core.
I ultimately want to give room for actual future development, so this is why I want to emphasize this early on, even if it won't happen right away. The app is still in its early stages, so hack-y code and poor code design can absolutely happen and I'm not ashamed of it because it allows room for improvement.
After a user installs Iris, the app cannot be opened due to the developer not being verified. This is because Iris builds are not code signed at the moment.
From my limited testing, here are the steps to resolve this issue:
Visual:
Once Iris builds are code signed, then there is no need to forcefully allow the app to run by bypassing the developer verification.
Note: You are responsible for allowing untrusted software on your machine. Although I myself have it installed without issues, others may be skeptical about bypassing developer verification to open the app.
When creating a new file, the folder file count node gets duplicated in the DOM inside the respective folder node.
Without handling the unnecessary duplication, it could potentially introduce more issues later on.
Finish fs-mod unit tests. This native module should end up with 100% code coverage.
The reference variable of the parent folder name randomly points to the wrong one. This causes the auto save listener to save the file in the wrong directory.
628678b almost eradicated this issue. However it's not 100% perfect. At the moment, it is a little difficult to reproduce this consistently.
Horizontal scrolling should be disabled by default, but reading mode is missing the overflow-x: hidden
property. The affected element is reading-mode-container
.
When you close and re-open a folder with an active file, the active state gets removed and becomes lost. This is due to the directory tree destroying and re-creating the nodes without an understanding of the current edited file.
Simply, there is no reference of the active file that the directory tree can trace back to. Thus, it causes a disconnection between the editor and the directory tree.
In a future release, I would like to add search functionality. A fuzzy search algorithm seems like a good fit.
Iris was proposed to have mode switching (#6).
While this sounds good on paper, this can get complicated when the modes are isolated. Since the internals of the app aren't very flexible or dynamic, I would need to do a major refactor or rewrite. I would consider a rewrite or major refactor later on but at the moment I don't think it's worth it.
Instead of isolated mode switching, it will be an editor switch instead. Basically the editor switching will behave exactly the same as the proposed mode switching except it won't be isolated. The folder and files will stay the same, except the editor. Users will need to take note of compatibility when editing their note in the basic or advanced editor.
Writing notes in the basic and advanced editors are interchangeable only if the note contains syntax up to the spec of CommonMark and/or doesn't contain custom syntax (HTML tags, GFM, diagram, math, etc).
TL;DR:
The proposed mode switching will be replaced with an editor switch instead. The functionality will be the same except files aren't isolated between the editors.
When a user does an action that causes a panic (Rust side), it crashes the app.
Since this isn't the ideal behaviour, I would like to handle this preferably as an alert of some sort on the JS side. However on the Rust side, I'm not sure if handling errors using the panic!
macro is appropriate for all cases.
In vague terms:
panic!
is meant for unrecoverable errors and it destroys the entire stack in memory after it gets executed, causing the app to crash by default
I'll have to take a closer look at this later on. Without handling these edge cases correctly, the flow of the app will diminish and the UX won't feel as robust.
The event listeners are hard coded and continuously run throughout the app's lifetime.
If you execute an event listener on a node without disposing its previous one (if applicable), you may end up with conflicts and bugs. Proper disposal will also prevent performance issues due to the removal of duplicated lifetime events.
I want to refactor/rework the event listeners so they won't end up with more issues later on. It's quite ugly to work with right now because I'm forcefully duplicating events in specific areas so they can be executed correctly.
I will most likely start on this once I finish the editor switching implementation.
Adding export options opens up more flexibility in terms of note portability and compatibility.
Here is a list of file exports that could be supported:
For exporting to HTML, I already have the tools to create the implementation so there is no need for other dependencies.
For exporting to PDF, there are two options:
I'm more familiar with Puppeteer but it seems like Playwright is the modern replacement, so I'll try it out.
In the settings, add a toggle to center editor content on full screen or a maximized window.
Users may prefer their editor content to be aligned on the screen differently when in full screen or a maximized window.
Options for alignment (full screen or maximized window):
At the moment, the directory tree is not sorted. The users' local file system determines the default sort order.
Sorting should be alphabetical for both folders and notes.
Whenever a change is made to the directory tree (add, delete, rename, etc), it should sort automatically right after the respective operation.
At the moment, only HTML tags and code blocks are preserved without modifications to the prosemirror-markdown
source code.
Here is a list of syntax/nodes that break (will be updated if I find any more):
Reproduction steps:
I'll add a screenshot later for the visual reproduction.
Iris should generate crash and debug information that is stored in a .txt
file.
Sometimes, users may have issues with the app for an unknown reason. Uploading the crash (if applicable) or debug information from the .txt
file when asking for assistance via Issues or Discussions can possibly help identify the core issue alongside their own explanation.
With that being said, Iris has to be able to detect an app crash in order for the .txt
file to be generated. The debug information is generated dynamically through a context menu or button press, so the mechanism isn't automatic. While these might be a little tricky to implement, it makes the app more native.
This quick generic implementation seems redundant but can be beneficial if used correctly.
It builds off of the current functions, encapsulates them, and exposes a single function.
interface ISettingFiles<T, K> {
//createDefaultSettings(): T,
//createDefaultDotSettings(): T,
//createDefaultAdvancedModeSettings(): T
createAllSettings(arg: T): K
}
class SettingFiles implements ISettingFiles<any, void> {
/**
* Create default settings
*/
private createDefaultSettings(): void {
fsMod._createFile(fsMod._baseDir("home") + "/Iris/.iris-settings.json", JSON.stringify(irisSettings));
}
/**
* Create dot settings
*/
private createDefaultDotSettings(): void {
fsMod._createFile(fsMod._baseDir("home") + "/Iris/.iris-dot-settings.json", JSON.stringify(irisDotSettings));
}
/**
* Create default advanced mode settings
*/
private createDefaultAdvancedModeSettings(): void {
fsMod._createFile(fsMod._baseDir("home") + "/Iris/.iris-advanced-editor-dot-settings.json", JSON.stringify(irisAdvancedModeDotSettings));
}
public createAllSettings(arg?: any): void {
this.createDefaultSettings();
this.createDefaultDotSettings();
this.createDefaultAdvancedModeSettings();
}
}
export const settingFiles = new SettingFiles();
Edit: updated naming
Currently the settings do not have persistence, so users will lose their selections when they quit the app. Using JSON to store and retrieve the persisted data is preferred.
I am able to cross-compile the native module for both x64 and arm64. I haven't tested the full compiled output, but at least I am able to compile for x64 on Apple Silicon.
By using
rustup target install x86_64-apple-darwin
and then updating the scripts in package.json
(say in fs-mod
):
"build-x64": "napi build --platform --release --target 'x86_64-apple-darwin'",
"build-arm64": "napi build --platform --release --target 'aarch64-apple-darwin'",
node_modules
will need to reinstalled before finalizing the compilation to a .dmg
. Also, the native module directory should only contain a single compiled file (either x64 or arm64) before reinstalling node_modules
because it can only detect one at a time. This requires manually deleting until a better process is found.
I want to supply both x64 and arm64 builds, so taking the extra steps are absolutely necessary.
Add font modifications to settings, mainly font sizes and font faces. Also, window resolution settings would be nice as well.
Being able to set the font size will allow the user to set the size based on their own preference. Locking the font size to a fixed value may cause issues on different resolutions.
Setting the font face is purely user preference but the recommended default font is Inter. There should be a handful of default fonts that users can choose from.
For window resolution settings, some users may prefer to have the app always open at [insert specific resolution here]. The default window resolution is 1200x750, but users may find this too small or too big. Therefore, being able to change the default resolution is an ideal choice as it can be persisted and loaded on startup.
2b26457 replaces Milkdown with vanilla ProseMirror.
After implementing the ProseMirror example setup to get started, I noticed performance improvements in areas that initially affected the UX:
performance.now()
).As for some of the reasons why I switched:
It is still in the early stages of the core implementation.
By default, editors have line wrapping enabled.
Adding an option to toggle line wrapping for the editors will give users a choice instead of forcing an opinionated default.
I'm planning on adding a reading mode which can be thought of as the preview window in other apps.
Reading mode will be under the kebab dropdown in the file directory tree beside the settings button. Having the modes in one central place allows users to switch between modes quickly and seamlessly.
Either remark or micromark will be used as the Markdown parser depending on what works the best for this. Unfortunately, markdown-rs doesn't have an extension for diagrams, so I have to resort to a JS Markdown parser for flexibility.
By default, reading mode will fill the editor space. I might add in an option for the advanced editor to allow reading mode side by side, but I'm not 100% sure because it's difficult to get right.
To help streamline common array operations, there should be a class that holds various generic methods to use on arrays. This can help mitigate hack-y logic and optimize code/performance a little easier.
Adding a context menu to show deleting and renaming would be nice to have.
Showing a context menu based on the node that is right-clicked to show appropriate options will allow the app to become more native. Don't get me wrong, although I like the UI elements (i.e., kebab, dropdown, etc) some of them don't fit the app very well.
There will be a UI-only navigation for now, so some of these might be replaced by a right-click context menu later on.
Once the app becomes more stable, I would like to add in custom themes. Using the persistence methods I'm currently using right now, the implementation shouldn't be too difficult.
Here are the steps for creating a custom theme. This is a quick draft, so it's not 100% confirmed.
${HOME}/${USER}/Iris
, there is a file called iris-custom-theme.css
. This is the only entry point that Iris can understand for loading custom themes. Do not change the file name.iris-custom-theme.css
in your favourite text-editor or IDE and modify it to your liking. The file will be commented with as much detail as possible.Custom
. Your theme should be loaded in the app based on the values inside iris-custom-theme.css
.Having custom themes would be nice since it allows users to share their own theme that defines what they consider "comfortable".
The auto save listener listens on the keyup
event and writes to the file without any delay (0ms). In large files, the constant invoking drastically slows down the editor when typing. Adding a debounce should help mitigate the problem.
Right now the auto save function writes to the disk at every 1000ms on a keyup
event. The issue is that the operation still proceeds even if the local note and the editor contents are the same.
On a high-level, users won't notice (if ever) the problems that it can cause to their disk long-term. The auto save function is quite lazy and silently terrible (good prototype though), so I want to implement it correctly.
Here's what the implementation could look like. Once I get to this point during the core cleanup, I should be able to come up with a working diff algorithm:
Extracting the editor content will be the costly part (front-end wise) but the rest of the operations will be done in the back-end.
I am planning on designing the app icon in Figma, which will be included in a future release.
As for the design, I'm a bit uncertain right now. When I get closer to drafting up the design, I will provide updates of my progress.
The app size (as of v0.2.0-dev-1) is ~500mb after installation.
This might seem a little excessive given that the unpacked asar is ~260mb.
The other half is Electron itself (~214mb), which is expected since it bundles Chromium and the related.
However, looking at the unpacked asar, the native module (~260mb) is the culprit here. I don't know if this is normal so I'll need to take a closer look at it later on.
Ideally, it would be nice to reduce the app size as much as possible in the full release.
A handful of paths in the unit tests are hard coded and don't automatically resolve.
I am not sure why I initially decided on hard coding these values, but I believe it was because of bad string concatenation causing failed tests and mismatches. Certainly did frustrate me at the time, so I ended up hard coding them.
As a reminder, baseDir()
from fsMod
will return the current base directory. In my case, baseDir("home")
returns /Users/alex
.
There are note file paths that are not easily replicable since they're a bit specific and random (not very test friendly). These could benefit from using the fixtures directory instead, so all test file paths are uniform and easier to debug.
It would be nice to add a toggle for hardware acceleration in the settings.
Users may have issues when running the app with hardware acceleration enabled due to GPU bugs or performance reasons. Having the option to disable it could help in those scenarios as a permanent or temporary measure.
By default, hardware acceleration is enabled.
Ref: https://www.electronjs.org/docs/latest/api/app#appdisablehardwareacceleration
It would be nice to bind the escape key to exit out of modal windows. This behaviour is common so it would be good to match standardized default key binds.
In future releases of Iris, I want to supply builds for Windows and Linux platforms alongside macOS. By supplying builds for other platforms, I have to ensure that the UI and UX is tailored to the respective operating system, giving each a (close to) native feel.
Here's a few possible window events that could be implemented:
For 2, either Electron process or Node process
could be useful here.
This isn't a complete list but these window events are somewhat common.
When a user quits the app, it should preserve the open file state and restore it on startup.
Persisting the file state will allow the app to feel more natural. This behaviour is common and should improve the UX.
Before or after the release of v0.2.0, the UI should be more consistent and look less prototype-y.
Here are some elements that could be improved:
Although the UI feels and looks pretty good right now, the small visual details can be off putting.
A modern and stock/native UI is what fits Iris the best. So when it's time to approach the UI rework, take these in consideration.
After trying out the block cursor theme for experimental purposes, I think it fits really well with the advanced editor.
The overall feel and styles are natural and the block cursor doesn't overpower the base themes.
Since I enjoy using the block cursor in my development environment, there is a bit of bias here. However, having the choice of switching between cursors for the advanced editor is a good perk for users that want it.
Here are two sample screenshots of what the cursors look like in the advanced editor:
If a user wants to insert a local image from the file system, the default path should resolve to Iris/Images
.
The folder helps keep all images in one place and makes it easy to backup the Iris
directory. This should avoid path issues for local images at least.
Drag and drop images is another feature that would be good to have. However, that will be in a future release at the earliest.
Themes, accent colours, and the alike should be under appearance for easier organization. This doesn't include fonts.
At the moment, themes are under its own category but it makes more sense to put it under appearance instead.
This will be updated periodically.
TBA
The app allows creating a file with a duplicate name residing in the same folder or different folder.
If the file resides in the same folder, it will overwrite the current file. The directory tree will show a duplicate file but the users file system will just overwrite the file since it can't understand duplicate files in the same folder.
If the file resides in a different folder, the open file state can't differentiate between the same file names even though they aren't in the same folder.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.