<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Notesnook Blog</title>
        <link>https://blog.notesnook.com</link>
        <description>Sharing the journey to building Notesnook, what we care about, the challenges we face &amp; and how we think through problems.</description>
        <lastBuildDate>Tue, 07 Apr 2026 12:01:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Feed for Node.js</generator>
        <image>
            <title>Notesnook Blog</title>
            <url>https://blog.notesnook.com/social-2.png</url>
            <link>https://blog.notesnook.com</link>
        </image>
        <copyright>All rights reserved 2026, Streetwriters (Private) Limited.</copyright>
        <item>
            <title><![CDATA[Notesnook Mobile v3.3.19]]></title>
            <link>https://blog.notesnook.com/notesnook-mobile-v3.3.19</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-mobile-v3.3.19</guid>
            <pubDate>Tue, 07 Apr 2026 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixes note title behavior, improves table editing on mobile, and resolves editor focus and file size validation issues.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook Mobile v3.3.19 improves note title behavior, table editing on mobile, and fixes a few annoying editor issues.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Better note title behavior</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Clearing a note title now keeps it empty instead of re-generating a title unexpectedly. We also adjusted the <code style="color:#ea0492">$headline$</code> formatting so the title is now derived from the first paragraph or heading instead of the whole note.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Improved tables on mobile</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Table interactions are now smoother on mobile, including better cell selection, more reliable horizontal scrolling, and improved in-cell editing and text selection.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><img alt="v3.3.19 table improvements" src="https://blog.notesnook.com/media/notesnook-mobile-3.3.19/improved_tables.gif" class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><figcaption class="w_full ta_center font-style_italic fs_s c_info">v3.3.19 table improvements</figcaption></figure>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">More predictable app launch behavior</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The app no longer randomly jumps into the editor on launch and now opens more consistently to the notes list.</p>
<h1>Other changes</h1>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed file size check shows error but continues adding the file to editor</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Note title headline format should get content from first paragraph</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Add reminders help page on help <a href="https://help.notesnook.com/reminders" target="_blank" class="c_accent">https://help.notesnook.com/reminders</a></li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Full Changelog</strong>: <a href="https://github.com/streetwriters/notesnook/compare/3.3.18-android...3.3.19-android" target="_blank" class="c_accent">https://github.com/streetwriters/notesnook/compare/3.3.18-android...3.3.19-android</a></p>]]></content:encoded>
            <author>Ammar Ahmed</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook Desktop v3.3.13]]></title>
            <link>https://blog.notesnook.com/notesnook-desktop-v3.3.13</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-desktop-v3.3.13</guid>
            <pubDate>Tue, 07 Apr 2026 12:01:22 GMT</pubDate>
            <description><![CDATA[Password reset UX improvement, headline format update, and multiple bug fixes.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook Desktop v3.3.13 includes password reset UX improvement, headline format update, and multiple bug fixes.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Improve UX on password reset flow</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We've added some changes in the password reset flow for better user experience</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The page will automatically redirect to login page after final step</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Back buttons are added on each form</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Better error messages when an invalid recovery key is inputted</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Title $headline$ format change</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">After various user requests last year, we changed how the note title's headline format worked. It would extract content from all nodes (paragraphs, headings, lists) until it hit the 150 character limit. Users have been asking for a while to change this behavior, and after some reflection we've changed it to only get content from the note's first paragraph/title and up to 60 characters.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Various bug fixes</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix callout's first heading being collapsed/expanded when clicking right next to it</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix plans page being cut off from the top</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">On change email, refresh the user so the new email is instantly visible. Also fixed the change email OTP timer not running on first attempt</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Handle crash on preview attachment</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Perform fuzzy search again if query contains non-alphanumeric characters</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Guard against undefined backStack/forwardStack in TabSessionHistory</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix HTML entities being shown in match result</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Full changelog: <a href="https://github.com/streetwriters/notesnook/compare/v3.3.12...v3.3.13" target="_blank" class="c_accent">https://github.com/streetwriters/notesnook/compare/v3.3.12...v3.3.13</a></p>]]></content:encoded>
            <author>Zulfiqar Ali</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook Mobile v3.3.18]]></title>
            <link>https://blog.notesnook.com/notesnook-mobile-v3.3.18</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-mobile-v3.3.18</guid>
            <pubDate>Mon, 30 Mar 2026 12:01:22 GMT</pubDate>
            <description><![CDATA[A critical fix to the attachment upload flow for new user signups.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook Mobile v3.3.18 fixes a critical bug in attachment upload flow for new user signups.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix attachment uploads for new user signups</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We identified an issue where new users signing up for Notesnook and immediately trying to upload attachments would encounter <code style="color:#ba00d3">ciphertext cannot be decrypted using that key</code> error. This was due to multiple attachment encryption keys being generated. We have fixed this issue by ensuring that the attachment encryption key is generated during the signup process instead of on the first attachment upload.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other fixes</h3>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix undefined id crash in subscription product items on iOS</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Full Changelog</strong>: <a href="https://github.com/streetwriters/notesnook/compare/3.3.17-android...3.3.18-android" target="_blank" class="c_accent">https://github.com/streetwriters/notesnook/compare/3.3.17-android...3.3.18-android</a></p>]]></content:encoded>
            <author>Ammar Ahmed</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook Desktop v3.3.12]]></title>
            <link>https://blog.notesnook.com/notesnook-desktop-v3.3.12</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-desktop-v3.3.12</guid>
            <pubDate>Sat, 28 Mar 2026 12:01:22 GMT</pubDate>
            <description><![CDATA[UX improvements, and a critical fix to the attachment upload flow for new user signups.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook Desktop v3.3.12 brings UX improvements, and a critical fix to the attachment upload flow for new user signups.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">UX improvements</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Pasting images from clipboard now properly checks if you are logged in before attempting to upload the image.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Sending email verification now shows toasts for success and failure states.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The navigation bar on the web app no longer un-collapses when a menu is still open.</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix attachment uploads for new user signups</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We identified an issue where new users signing up for Notesnook and immediately trying to upload attachments would encounter <code style="color:#ba00d3">ciphertext cannot be decrypted using that key</code> error. This was due to multiple attachment encryption keys being generated. We have fixed this issue by ensuring that the attachment encryption key is generated during the signup process instead of on the first attachment upload.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Full changelog: <a href="https://github.com/streetwriters/notesnook/compare/v3.3.11...v3.3.12" target="_blank" class="c_accent">https://github.com/streetwriters/notesnook/compare/v3.3.11...v3.3.12</a></p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook Mobile v3.3.17]]></title>
            <link>https://blog.notesnook.com/notesnook-mobile-v3.3.17</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-mobile-v3.3.17</guid>
            <pubDate>Wed, 25 Mar 2026 12:01:22 GMT</pubDate>
            <description><![CDATA[Telegram icon in Settings, Improved diacritic-agnostic search, Better fuzzy matching for attachments search, Smooth note switching, Conflict handling for locked notes, YouTube embed fix and security patches]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook Mobile v3.3.17 brings Telegram icon in Settings, improved diacritic-agnostic search, better fuzzy matching for attachments search, smooth note switching, conflict handling for locked notes, YouTube embed fix, and security patches.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Improved note switcing</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Note switching has been improved to remove the flicker and make it feel smooth.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><img alt="v3.3.16" src="https://blog.notesnook.com/media/notesnook-mobile-3.3.17/note-glitch-before.gif" class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><figcaption class="w_full ta_center font-style_italic fs_s c_info">v3.3.16</figcaption></figure><figure class="w_full m_0 d_flex flex-d_column ai_center"><img alt="v3.3.17" src="https://blog.notesnook.com/media/notesnook-mobile-3.3.17/note-glitch-after.gif" class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><figcaption class="w_full ta_center font-style_italic fs_s c_info">v3.3.17</figcaption></figure></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Search improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Support for diacritic-agnostic search was introduced in <a href="https://blog.notesnook.com/notesnook-v3.0.18" target="_blank" class="c_accent">v3.0.18</a>. However, it was not handled correctly in the revamped search experience. This has now been fixed. Additionally, a bug that caused searches with certain special characters (such as <code style="color:#c4096d">[</code> or <code style="color:#ea0eb3">\</code>) to return empty results or crash the app has been resolved.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Special word separator handling has also been improved across all areas using fuzzy search (including notebooks, tags, and attachments). For example, searching for “a b” will now return variations like “a_b” and “a-b”.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Security patches</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We received one security reports this month, which has been addressed:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Note titles were not properly escaped when sharing to Notesnook using Share Extension. This could allow malicious HTML in user-generated titles to execute. Titles are now safely escaped before rendering. (Thanks <a href="https://github.com/ngocnn97" target="_blank" class="c_accent">Nolan42</a> for responsible disclosure!)</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>No action is required beyond updating to this version.</strong></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other fixes</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix iOS app remains stuck when restoring backup from list of backups in the app or when creating a backup</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Allow secure local https with user installed CA on android</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix app gets locked when requesting notification permission for upload progress</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Add missing telegram icon in Settings</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix linked note not opening from attachment properties</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed top bar items in note properties show irregular space and do not follow pagination rules. Each page should fill the whole width of the available space.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Unlocking a locked note that is not in any vault has been fixed now</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">You can now preview and resolve merge conflicts in locked notes</li>
</ol>]]></content:encoded>
            <author>Ammar Ahmed</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook Desktop v3.3.11]]></title>
            <link>https://blog.notesnook.com/notesnook-desktop-v3.3.11</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-desktop-v3.3.11</guid>
            <pubDate>Wed, 25 Mar 2026 12:01:22 GMT</pubDate>
            <description><![CDATA[Improved diacritic-agnostic search, better fuzzy matching, desktop auto-update control, YouTube embed fix, and security patches]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook Desktop v3.3.11 brings improved diacritic-agnostic search, better fuzzy matching for attachments search, desktop auto-update control, YouTube embed fix, and security patches.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Desktop fixes</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Disabling auto-update in settings now also disables the update checker. The app will no longer check for updates automatically.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">An issue with YouTube embeds not working was addressed in previous releases; however, “Error 153” persisted on Desktop. This has now been fully resolved.</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Search improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Support for diacritic-agnostic search was introduced in <a href="https://blog.notesnook.com/notesnook-v3.0.18" target="_blank" class="c_accent">v3.0.18</a>. However, it was not handled correctly in the revamped search experience. This has now been fixed. Additionally, a bug that caused searches with certain special characters (such as <code style="color:#c4096d">[</code> or <code style="color:#ea0eb3">\</code>) to return empty results or crash the app has been resolved.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Special word separator handling has also been improved across all areas using fuzzy search (including notebooks, tags, and attachments). For example, searching for “a b” will now return variations like “a_b” and “a-b”.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Security patches</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We received two security reports this month, both of which have been addressed:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Note titles were not properly escaped in the web and desktop diff viewer. This could allow malicious HTML in user-generated titles to execute. Titles are now safely escaped before rendering. (Thanks <a href="https://github.com/ngocnn97" target="_blank" class="c_accent">Ngocnn97</a> for responsible disclosure!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Notes created via the web clipper could include malicious scripts within iframes. This issue has been fixed by securely sandboxing the clipped content. (Thanks <a href="https://github.com/ngocnn97" target="_blank" class="c_accent">Ngocnn97</a> for responsible disclosure!)</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>No action is required beyond updating to this version.</strong></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other fixes</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Escape command titles in command palette</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix editor title temporarily flickering on tab switch</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix feature title margin in feature dialog</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Remove vertical offset on link hover popup</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix typo in import result text</li>
</ol>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.3.9]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.3.9</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.3.9</guid>
            <pubDate>Tue, 10 Mar 2026 12:01:22 GMT</pubDate>
            <description><![CDATA[Audio attachment playback, attach images as files, code block language memory, sorted tags, and 40+ fixes across all platforms]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.3.9 brings audio attachment playback, image-as-file attachments, code block language memory, sorted tags on mobile, and fixes 40+ bugs across all platforms!</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Audio attachment playback</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now play audio files directly inside your notes without downloading them first. Audio attachments render as an inline audio player, so you can listen to voice memos, recordings, or any other audio file right in the editor.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To attach an audio file, use the attachment menu in the toolbar and select your audio file. It will appear as a playable widget in your note.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Attach images as files</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Images can now be inserted as file attachments instead of being embedded inline. This is useful when you want to keep an image alongside your note without it taking up space in the editor view.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To attach an image as a file, open the attachment menu in the toolbar and choose the file option when inserting an image.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Code block language memory</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The editor now remembers the last-used code block language and pre-selects it the next time you insert a code block. This saves time when writing multiple code blocks in the same language throughout a note.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Sort assigned tags to top on mobile</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When viewing or editing the tags of a note on mobile, tags already assigned to the note now appear at the top of the list. This makes it much easier to manage tags without scrolling through the full tag list.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Delete data for logged-out users</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Users who are not logged in can now delete all local app data from the settings. This is useful for clearing notes and data from a shared or temporary device without needing an account.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Find this option in Settings under "Delete data".</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other improvements</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Improved horizontal rule styling</strong> — Horizontal rules in the editor are now thinner and more refined, blending better with your content</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Reminder dialog improvements</strong> — The reminder dialog has been redesigned for a cleaner and more consistent experience</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Reduce editor title padding</strong> — Editor title &amp; content on web and desktop now uses consistent sizing and padding for a more compact look</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Prioritize environment variables for server URL</strong> — Self-hosters can now override the server URL via environment variables, which take priority over other configuration</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As with all our releases, we've fixed a range of bugs and improved overall stability. Here are the notable fixes:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed external links opening inside the app instead of the default browser on desktop (thanks <a href="https://github.com/Copilot" target="_blank" class="c_accent">@Copilot</a> reviewed by <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed vault not being marked as created when locking a note for the first time</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed vault not being marked as created on login when it already exists</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed attachment not marked as uploaded after lock/unlock</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed web and desktop app crashes (thanks <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed crash on drag-and-drop file in Flatpak build</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed text not being selectable across the full editor height (thanks <a href="https://github.com/keysmashes" target="_blank" class="c_accent">@keysmashes</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed unable to change zoom level back to 1.0 (thanks <a href="https://github.com/littleKitchen" target="_blank" class="c_accent">@littleKitchen</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed toolbar item indentation when moving between groups (thanks <a href="https://github.com/ArjunCodess" target="_blank" class="c_accent">@ArjunCodess</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed properties panel rendering for locked sessions</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed check icon alignment in note color properties panel</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed font family being lost on new lines (thanks <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed backticks creating inline code even with Markdown Shortcuts disabled (thanks <a href="https://github.com/Copilot" target="_blank" class="c_accent">@Copilot</a> reviewed by <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed TOC active heading detection for headings nested inside callout blocks (thanks <a href="https://github.com/Copilot" target="_blank" class="c_accent">@Copilot</a> reviewed by <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed checklist and blockquote indent alignment</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed empty embed URLs and code being allowed in embeds</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed archived notes not appearing in attachment linked notes</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed account password changing (thanks <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Removed orphaned attachments during trash cleanup</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed random crash when opening a notebook on mobile</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed toast context in the color picker on mobile</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed attachment upload error ("Failed to delete file") on mobile</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed note loading getting stuck on mobile</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed foreground/background switch starting a new attachment cache download group on mobile</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed vault with biometrics enabled not locking the note on mobile</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed note title being editable in read-only mode on mobile</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed native Android crashes found in Play Console vitals</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed reachability URL on mobile</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Security fixes</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed HTML escaping in Twitter/X embed source (thanks <a href="https://x.com/x4sh3s" target="_blank" class="c_accent">@x4sh3s</a> for responsible disclosure!). <strong>No action required beyond updating to this version.</strong></li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full <a href="https://github.com/streetwriters/notesnook/compare/v3.3.8...v3.3.9" target="_blank" class="c_accent">commit history</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.3.8]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.3.8</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.3.8</guid>
            <pubDate>Wed, 28 Jan 2026 12:01:22 GMT</pubDate>
            <description><![CDATA[Expiring notes, table CSV export/import, customizable editor line height, Android home shortcuts, and 80+ bug fixes across all platforms]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.3.8 introduces expiring notes, table CSV export/import, customizable editor line height, Android home shortcuts, and fixes 80+ bugs across all platforms!</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Expiring notes (Pro)</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notes can now expire automatically after a set date. When a note expires, it gets moved to trash. This is useful for temporary notes, drafts, or time-sensitive information that should be automatically cleaned up after a certain date.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To set an expiry date, open note properties and select "Set expiry date". You can set any date in the future, and the note will automatically move to trash when that date is reached.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Export/import tables as CSV (Pro)</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now export tables to CSV files and import CSV files as tables directly in the editor. This makes it easier to work with spreadsheet data in your notes.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To export a table, right-click on it and select "Export to CSV". To import, use the same menu and select "Import from CSV".</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Customizable editor line height</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Control the spacing between lines in the editor with the new line height setting. Fine-tune the line height with float-level precision to perfectly match your reading preference.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Find this setting in Settings &gt; Editor &gt; Line height.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Android home launcher shortcuts (Pro)</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Android users can now add shortcuts for notebooks, tags, notes, and colors directly on their home screen for quick access. Long press the Notesnook app icon and drag a shortcut to your home screen.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Day &amp; week format settings</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Customize how dates are displayed throughout the app with new day and week format options. You can now choose your preferred first day of the week and date format. Additionally, use the new <code style="color:#ce0671">$day$</code> title format and <code style="color:#ef17c8">/day</code> command for daily notes.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Edit note creation date</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now edit a note's creation date from the properties panel. This is useful when importing notes from other apps or when you want to backdate entries.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">New keyboard shortcuts</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Several new keyboard shortcuts have been added to improve your workflow:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Move line up/down</strong> - Move the current line or selection up/down with Alt+Up/Down (Windows/Linux) or Option+Up/Down (Mac)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Clear current node</strong> - Remove formatting from the current node with Ctrl+Backspace (Windows/Linux) or Cmd+Backspace (Mac)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Save note</strong> - Quickly save from the status icon click or tab menu</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Copy images</strong> - Copy images with Ctrl+C (Windows/Linux) or Cmd+C (Mac)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Command palette</strong> - Now accessible via a button in the status bar</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Title in note history</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Note history now includes the title in each session, making it easier to track changes to both content and titles over time. When viewing note history, you can see exactly what the title was at each point in time.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other improvements</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Multipart uploads on mobile</strong> - Improved reliability for large file uploads on iOS and Android</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Customizable monograph title</strong> - Set custom titles for your published monographs</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Preview attachments</strong> - Preview attachments directly in the attachment manager before downloading</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Conflicts on locked notes</strong> - Sync conflicts now properly show on locked notes</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Improved sync reliability</strong> - Various improvements to make syncing more reliable</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Collapsible heading performance</strong> - Significantly improved performance when working with many collapsible headings</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As with all our releases, we've fixed a bunch of bugs and improved the overall user experience. Here are some notable fixes:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed file picker and PDF preview issues on iOS and Android (thanks <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed iOS crash when using biometric unlock</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed keyboard opening on app launch if locked note is opened</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed sidebar close gesture on iOS</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed color picker wheel images not loading</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed notification notes being created on every app launch</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed app crash when restoring invalid backup files</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed wrong trial end date shown in settings</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed editor and note title resizing when window resizes</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed TOC headings alignment (thanks <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed empty command palette/quick open when locked note is active</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed duplicate block-id on splitting nodes</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed desktop starting minimized on auto-startup</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed YouTube embeds erroring with code 153</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed icons not aligning in lists and callouts with custom line height</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed hidden collapse/expand icon when callout title is empty</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed web clip on mobile not including URL &amp; date clipped</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed shortcuts not updating in side menu</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed share sheet bottom padding</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed search scroll not working on switching editors</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed reminder sorting and dialog formatting (thanks <a href="https://github.com/NeedsChloesure" target="_blank" class="c_accent">@NeedsChloesure</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed themes not installing and improved theme server performance</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">PDF previews now work correctly on iOS</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Cleanup downloaded cache files on startup</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full <a href="https://github.com/streetwriters/notesnook/compare/v3.3.7...v3.3.8" target="_blank" class="c_accent">commit history</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.3.6]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.3.6</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.3.6</guid>
            <pubDate>Wed, 17 Dec 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[Wrapped 2025, monograph analytics, table improvements on mobile, new keyboard shortcuts and 50+ bug fixes!]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.3.6 brings Wrapped 2025, monograph analytics, table improvements on mobile, new keyboard shortcuts and 50+ bug fixes!</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Wrapped 2025</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook Wrapped 2025 is your personal recap of the year you spent with Notesnook. Available throughout December and accessible from the sidebar, it highlights your activity, stats and how you used Notesnook this year. Here's a sneakpeak:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/wrapped.Bq5qkLDT.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/wrapped.DJTeRHgE.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/wrapped.DJTeRHgE.png" srcset="https://blog.notesnook.com/assets/static/wrapped.DJTeRHgE.png 640w" sizes="100vw" alt="Wrapped 2025 sneakpeak" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Wrapped 2025 sneakpeak</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">All stats are generated 100% locally on your device, ensuring your privacy is preserved.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Don't forget to share your Wrapped 2025 with us on Discord and X!</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Special thanks to <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> for coming up with this idea and implementing it!</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Monograph analytics</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now see your Monograph's view count. Note: this view counts are recorded after this release.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Table improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Tables on mobile (and touch devices) have been significantly improved:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Resize columns directly via touch just like on web/desktop.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Cell selection now works as expected.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><img alt="" src="https://blog.notesnook.com/media/notesnook-v3.3.6/table-mobile.gif" class="mt_3"></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Many table related bugs were also fixed, including an issue where read-only tables couldn't be scrolled.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Editor improvements</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Collapsible headings improvements</strong> - Fixed alignment issues with multi-line headings (works perfectly in both LTR and RTL), hide horizontal rules under collapsed headings, and better behavior when changing heading levels.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Improved inline code styling for better readability.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Completed checklist items now have a fade effect for better visual clarity.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Twitter/X embed support - You can now embed tweets directly in your notes.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Added search and replace shortcut (Cmd/Ctrl + Alt/Option + F).</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Added text color toggle keybinding (Cmd/Ctrl + Alt/Option + C).</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Attachment upload improvements on iOS/Android</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Attachment uploads on mobile devices have been made much more reliable:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Uploads now continue in the background when you switch apps.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Show notification for in-progress uploads.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Improved overall upload reliability.</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As with all our releases, we've fixed a bunch of bugs and improved the overall user experience. Here are some notable fixes:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed file uploading not working in Safari</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed file drag overlay stuck forever (now closable with click or ESC key)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Show spaces properly in note, notebook, tag, and reminder list titles</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed chunk calculation errors that could cause upload issues (thanks <a href="https://github.com/needschloesure" target="_blank" class="c_accent">@needschloesure</a>!)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Archive notes directly by dropping them on the Archive nav item</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed user keys re-encryption when changing password</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full <a href="https://github.com/streetwriters/notesnook/compare/v3.3.5...v3.3.6" target="_blank" class="c_accent">commit history</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook & Tuta partnership]]></title>
            <link>https://blog.notesnook.com/notesnook-and-tuta</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-and-tuta</guid>
            <pubDate>Mon, 15 Dec 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[Notesnook Circle is expanding with Tuta, a secure email & calendar service that prioritizes user privacy.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Our goal with Notesnook Circle is to partner with privacy-focused services that are most essential to our users. Over the past year, we have partnered with some amazing services:</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://ente.io" target="_blank" class="c_accent">Ente</a> - private photo &amp; video storage</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://addy.io" target="_blank" class="c_accent">Addy.io</a> - email aliasing service</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://kagi.com" target="_blank" class="c_accent">Kagi</a> - private search engine</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And now we are thrilled to add <a href="https://tuta.com/friends" target="_blank" class="c_accent">Tuta</a> to this list of amazing partners.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Tuta is a secure email &amp; calendar service that prioritizes user privacy. A lot of our users already use Tuta for their email needs, so this partnership was a natural fit.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>As a paying Notesnook member, you can now get 25% off your first year on any Tuta plan. Similarly, Tuta users can now enjoy a 25% discount on the first year of Notesnook Pro plan.</strong></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">How to Redeem</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Open Notesnook</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Go to Settings &gt; Notesnook Circle</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Click on "Redeem code" under Tuta</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Use the direct link given to claim your discount. If that doesn't work, you can also append the code to this link <code style="color:#cc1093">https://app.tuta.com/signup/#token=</code> as an alternative.</li>
</ol>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook & Addy.io partnership]]></title>
            <link>https://blog.notesnook.com/notesnook-and-addyio</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-and-addyio</guid>
            <pubDate>Mon, 08 Dec 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[Notesnook Circle is expanding with Addy.io, an open-source email aliasing service designed to give you complete control over your email privacy.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook Circle is expanding with <a href="https://addy.io" target="_blank" class="c_accent">Addy.io</a>, an open-source email aliasing service designed to give you complete control over your email privacy. Thousands of Notesnook users already use Addy.io to protect their inbox and we are super excited to have them as our partner in Notesnook Circle.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>As a paying Notesnook member, you can now get 25% off your first year on any Addy.io plan. Similarly, Addy.io users can now enjoy a 25% discount on the first year of Notesnook Pro plan.</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read Addy.io's announcement <a href="https://addy.io/blog/new-perk-for-addy-io-subscribers-25-percent-off-notesnook/" target="_blank" class="c_accent">here</a>.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">How to Redeem</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Open Notesnook</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Go to Settings &gt; Notesnook Circle</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Click on "Redeem code" under Addy.io</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The discount code should be usable during checkout on Addy.io.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook & Ente partnership]]></title>
            <link>https://blog.notesnook.com/notesnook-and-ente</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-and-ente</guid>
            <pubDate>Wed, 05 Nov 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[As a paying Notesnook user, you can now get 25% off your first year of Ente. Similarly, Ente users can now enjoy a 10% recurring discount on the Notesnook Pro Yearly plan.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Last month we launched <a href="https://notesnook.com/circle" target="_blank" class="c_accent">Notesnook Circle</a>, our new initiative to partner with privacy-focused services and apps that align with our values. Our first partner in this initiative was <a href="https://kagi.com" target="_blank" class="c_accent">Kagi</a>, and now we are partnering up with <a href="https://ente.io" target="_blank" class="c_accent">Ente</a> to make Notesnook Circle even better!</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Notesnook &amp; Ente</h2>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/notesnook-with-ente.BLLiWIGe.webp 320w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/notesnook-with-ente.DZhotET4.png 320w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/notesnook-with-ente.DZhotET4.png" srcset="https://blog.notesnook.com/assets/static/notesnook-with-ente.DZhotET4.png 320w" sizes="100vw" alt="Notesnook &amp; Ente Partnership" style="max-width:320px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Notesnook &amp; Ente Partnership (image courtesy of Ente)</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Ente is a privacy-focused and end-to-end encrypted photo and video storage service that ensures only you can access your memories. Ente shares our vision of a better, more private internet, and we are thrilled to have them as our second partner in Notesnook Circle.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>As a paying Notesnook member, you can now get 25% off your first year of any Ente plan. Similarly, Ente users can now enjoy a 10% recurring discount on the Notesnook Pro Yearly plan.</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To avail of this offer, simply visit the <a href="https://notesnook.com/circle" target="_blank" class="c_accent">Notesnook Circle</a> page and follow the instructions to redeem your benefits.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Building a united front for privacy: Notesnook & Kagi partnership]]></title>
            <link>https://blog.notesnook.com/introducing-notesnook-circle</link>
            <guid isPermaLink="false">https://blog.notesnook.com/introducing-notesnook-circle</guid>
            <pubDate>Tue, 21 Oct 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[As a paying Notesnook user, you can now get 3 months free of the Kagi Professional plan. Similarly, Kagi users can now enjoy a 10% recurring discount on the Notesnook Pro Yearly plan.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If the objective is privacy then the only way forward is through collective effort. As humans, it is our natural instinct to gather around a common cause, to form a community, a grand identity we can be proud to be part of.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Unfortunately, on the Internet everyone and everything is isolated. Each service is trying to build its own walled garden; from Apple to Google to Facebook and Microsoft. This goal is starkly converse to what we as humans seek, because most of us don't fit into a single ecosystem. We use multiple services, our lives are scattered across a host of different apps, and we love the freedom to choose.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Wouldn't it be awesome if the services you loved to use, that you trusted, came together to form a united front for privacy? A coalition of apps and services that work together to protect your data, respect your privacy, and give you control over your digital life?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">That's our vision behind Notesnook Circle.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Notesnook &amp; Kagi</h2>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/nn-kagi.DSKQXRyk.webp 640w, https://blog.notesnook.com/assets/static/nn-kagi.u_C4iWZU.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/nn-kagi.BJwPjjDR.png 640w, https://blog.notesnook.com/assets/static/nn-kagi.Dz3U-CTf.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/nn-kagi.BJwPjjDR.png" srcset="https://blog.notesnook.com/assets/static/nn-kagi.BJwPjjDR.png 640w, https://blog.notesnook.com/assets/static/nn-kagi.Dz3U-CTf.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Notesnook &amp; Kagi Partnership" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Notesnook &amp; Kagi Partnership (image courtesy of Kagi)</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are excited to announce our partnership with <a href="https://kagi.com/" target="_blank" class="c_accent">Kagi</a>, a privacy-focused search engine that puts user experience and privacy above all else. Kagi shares our vision of a better, more private internet, and together we aim to create a seamless experience for users who value their privacy.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Aside: I have been personally using Kagi products, especially the Orion browser, for the past several months and it is one of the most fantastic browsing experiences I have ever had so I can't recommend Kagi enough.</p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>As a paying Notesnook member, you can now get 3 months free of Kagi's Professional plan, which includes unlimited searches, Kagi Assistant, and more. Similarly, Kagi users can now enjoy a 10% recurring discount on the Notesnook Pro Yearly plan.</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To avail of this offer, simply visit the <a href="https://notesnook.com/circle" target="_blank" class="c_accent">Notesnook Circle</a> page and follow the instructions to redeem your benefits.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Looking Ahead</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is just the beginning. We are actively working on expanding the Notesnook Circle by partnering with more privacy-focused services and apps. If you think some privacy-focused service would be a great fit for Notesnook Circle, please reach out to us at support[at]streetwriters.co. We would love to hear from you!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Our goal with Notesnook Circle is to prioritize quality over quanitity. We don't want to recommend something we would personally never use. A small but strong network of trusted partners who share our vision for a better, more private internet is far more preferable to an explosive list built only for marketing.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.3.0]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.3.0</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.3.0</guid>
            <pubDate>Sat, 04 Oct 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[Filters in search, editor stats, new pricing plans and much more!]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.3.0 introduces filters in search, editor stats, and a lot of bug fixes! This release mainly adds <a href="https://blog.notesnook.com/introducing-new-pricing-plans" target="_blank" class="c_accent">the new pricing plans</a> but we also added a few other improvements.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Filters in search</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Search now supports field filters like:</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#cc00be">content</code>: <code style="color:#ce0ab7">&lt;text&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ba0063">title</code>: <code style="color:#ce0ab7">&lt;text&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ea07a2">favorite</code>: <code style="color:#c10786">&lt;true|false&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#e0007f">pinned</code>: <code style="color:#c10786">&lt;true|false&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#9c06ce">locked</code>: <code style="color:#c10786">&lt;true|false&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#b71259">archived</code>: <code style="color:#c10786">&lt;true|false&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#c90481">readonly</code>: <code style="color:#c10786">&lt;true|false&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#a100c9">tag</code>: <code style="color:#ce0ab7">&lt;text&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ed177e">color</code>: <code style="color:#ce0ab7">&lt;text&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#1c1c1c">edited_after</code>: <code style="color:#e0067e">&lt;date&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#262626">created_after</code>: <code style="color:#e0067e">&lt;date&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#161616">created_before</code>: <code style="color:#e0067e">&lt;date&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#000000">edited_before</code>: <code style="color:#e0067e">&lt;date&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#070707">in_notebook</code>: <code style="color:#c10786">&lt;true|false&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#c80ecc">tagged</code>: <code style="color:#c10786">&lt;true|false&gt;</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#e50bc1">colored</code>: <code style="color:#c10786">&lt;true|false&gt;</code></li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The UX is still pretty raw but we are releasing this to gather early feedback. Consider it a beta feature.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Editor stats</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now see the character count, word count, total spaces, and paragraphs count in the status bar below the editor.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and minor improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As with all our releases, we've fixed a bunch of bugs and improved the overall user experience. Thanks to our beta users you'll find this release much more stable and performant.</p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.2.4...v3.3.0" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Introducing new pricing plans]]></title>
            <link>https://blog.notesnook.com/introducing-new-pricing-plans</link>
            <guid isPermaLink="false">https://blog.notesnook.com/introducing-new-pricing-plans</guid>
            <pubDate>Sat, 04 Oct 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[We’re adjusting our prices to keep improving Notesnook while keeping it affordable and private for everyone.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">First things first, our prices are changing. Nobody likes price hikes but we want to be 100% transparent about why this is happening and what it means for you.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There's a lot that goes into keeping a product like Notesnook sustainable: from reducing our hosting costs to writing better code that requires fewer man-hours to maintain. But eventually, no matter what we do, we have to raise our prices in order to keep moving forward.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For context, Notesnook Pro has been offered at a really competitive price of just $4.49/mo. This includes unlimited storage (files &amp; images), unlimited devices, monographs, the complete rich text editor experience, and anything else we might add in the future.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But as you can guess, this just isn't sustainable. First of all, our unlimited storage offering is truly unlimited (you can check the code!) which makes it impossible to maintain in the long term. We added this as a way to set us apart from what everyone else was offering, but I think Notesnook has grown to become so much more than its competitor without leaning on shiny labels like that.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Raising prices will help us keep Notesnook 100% private, independent, and sustainable — no investors, no ads, no data selling. Every dollar will go into making Notesnook better for you, not bigger for someone else.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">What does this mean for existing subscribers?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you have an active subscription (i.e. not canceled and not purchased via a gift card), you won't have to do anything. Any price change will not affect you and you can continue using everything listed in Notesnook Pro plan without interruption.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For those who are inactively subscribed i.e. canceled or by using a gift card, you will have to pay the new price in order to continue using the Pro features.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Now that we’ve talked about the why, let’s look at what’s actually changing.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">The new pricing plans</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are moving away from the single-plan model and introducing three new plans:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Essential ($1.99/mo or $19.99/yr)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Pro ($6.99/mo or $69.99/yr)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Believer ($8.99/mo or $89.99/yr)</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>Note: Prices shown in USD; equivalent local prices may vary based on taxes and exchange rates. To view exact price for your country, visit our website.</em></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can view the full breakdown on <a href="https://notesnook.com/pricing" target="_blank" class="c_accent">our website</a>, but here's a quick summary:</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Essential plan</strong> includes 1GB of storage per month, 100MB maximum file size, unlocks all editor features, in addition to a few other "essential" things.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Pro plan</strong> includes 10GB of storage per month, 1GB maximum file size, and unlocks everything.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Believer plan</strong> includes 25GB of storage per month and is meant for diehard fans of Notesnook ;).</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Storage limit resets on the 1st of each month. You can always manage your existing uploads whenever you like.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Here's a table a user on Discord made comparing all plans:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/pricing-table.byLMUpcT.webp 640w, https://blog.notesnook.com/assets/static/pricing-table.DlpDvd8W.webp 1024w, https://blog.notesnook.com/assets/static/pricing-table.CGH86a94.webp 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/pricing-table.CGpBVx_4.png 640w, https://blog.notesnook.com/assets/static/pricing-table.CfXQmNsW.png 1024w, https://blog.notesnook.com/assets/static/pricing-table.C_fhhrK2.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/pricing-table.CGpBVx_4.png" srcset="https://blog.notesnook.com/assets/static/pricing-table.CGpBVx_4.png 640w, https://blog.notesnook.com/assets/static/pricing-table.CfXQmNsW.png 1024w, https://blog.notesnook.com/assets/static/pricing-table.C_fhhrK2.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Pricing plans comparison" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Pricing plans comparison</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For those who prefer long-term stability or want to invest in Notesnook’s future, we’ve also added a 5-year plan for both the Pro and Believer tiers.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">What about the <strong>free plan</strong>?</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">The free plan</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The Notesnook Basic plan has always been generous, but we are making it even better:</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">You can now upload images and files (<strong>up to 50 MB per month</strong>) even on the free plan.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Vault is now available to free users.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">All export formats are now available to free users.</li>
</ul>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Moving away from unpaid trials</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Unpaid trials (aka "no credit card required trials") have been a huge source of abuse for us. It also confuses users who actually want to upgrade. To resolve all these issues, you will now be required to enter your payment information in order to enjoy a free trial.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Just to be clear, trials are still free but now you need to enter your payment method.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Notesnook for Education</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have over 5K students &amp; teachers enjoying the Notesnook for Education discount. It's safe to say that Notesnook is a lifeline for a lot of teachers and students out there. To keep things affordable, we are now offering our Notesnook Pro plan at a fixed price of $19.99 for one year to all students and teachers.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We know many educators won’t like this change, but we believe it’s a fair adjustment that will help us keep improving Notesnook for everyone.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Regional pricing</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook is still regionally priced but only for the Notesnook Pro Yearly plan. All other plans are offered at the same price to everyone around the world.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">More subscription controls</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook is one of the very few apps that offer single-click refunds. We are now also offering single-click plan changing with prorated pricing. You can now jump from the Essential plan to Believer plan (or any other plan) right from inside the app without contacting us.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In addition to that, we’ve also made it easier to disable auto-renew for your subscription: just a single toggle. Hopefully, this will help you stay in control and avoid any surprise charges.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">FAQs</h2>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">When does the price change take effect?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">These price changes will take effect with v3.3 of Notesnook.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">What happens if my old subscription downgrades accidentally due to payment failure?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If your payment fails or your subscription accidentally expires, we'll do our best to help you retain the same price but we make no promises. You can reach out to us via email in case something like this happens and we'll help you out.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Will my existing plan automatically move to a new one?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">No. If you have an active subscription, nothing changes for you.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">The future</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have huge plans for Notesnook, and many things are already under development. These include:</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Self hosting</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Third-party audit</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Inbox API (to allow sending data into Notesnook from other apps)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Monographs v2</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is just the beginning.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Can Evernote employees read your notes?]]></title>
            <link>https://blog.notesnook.com/can-evernote-employees-read-your-notes</link>
            <guid isPermaLink="false">https://blog.notesnook.com/can-evernote-employees-read-your-notes</guid>
            <pubDate>Mon, 01 Sep 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[Any company claiming to protect your privacy without end-to-end encryption is just gaslighting you. Don't depend on privacy policies to protect your privacy.]]></description>
            <content:encoded><![CDATA[<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Disclaimer</strong>: I am the co-founder of Notesnook, an end-to-end encrypted note taking app. I have tried to be as objective as possible but there may be some bias.</p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Privacy by design is far superior to privacy by promise. Companies like Google, Notion, Evernote, and in fact, any company that does not ensure end-to-end encryption all <em>promise</em> to never read your personal data but do nothing to actually prevent it.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Last week, I posted about this on our X account but didn't get a chance to elaborate:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/x-post.CRLavmhE.webp 605w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/x-post.BeF-3COu.png 605w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/x-post.BeF-3COu.png" srcset="https://blog.notesnook.com/assets/static/x-post.BeF-3COu.png 605w" sizes="100vw" alt="The post on X" style="max-width:605px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">The post on X</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Even the /r/Evernote moderators <a href="https://www.reddit.com/r/Evernote/comments/1mwvx8h/comment/na53lav/" target="_blank" class="c_accent">don't know this</a> (or they don't want to acknowledge it):</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/reddit-comment.CY5pp76B.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/reddit-comment.Dc1aPzcB.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/reddit-comment.Dc1aPzcB.png" srcset="https://blog.notesnook.com/assets/static/reddit-comment.Dc1aPzcB.png 640w" sizes="100vw" alt="EVERNOTE EMPLOYEES DO NOT READ YOUR NOTES. Do not spread fear in this community." style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Comment on /r/Evernote</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In this blog post, I want to explain how Evernote always has access to your notes, why privacy policies are not guarantees, and how this can lead to user privacy violations. By the end of this blog post, hopefully you'll understand why any company claiming to protect your privacy without end-to-end encryption is just gaslighting you.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Can Evernote employees really read my notes?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In 2016, Evernote <a href="https://web.archive.org/web/20161215140949/https://help.evernote.com/hc/en-us/articles/235660588" target="_blank" class="c_accent">updated their privacy policy</a> to include:</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The latest update to the Privacy Policy <strong>allows some Evernote employees to exercise oversight of machine learning technologies</strong> applied to account content, subject to the limits described below, for the purposes of developing and improving the Evernote service.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is primarily to make sure that our machine learning technologies are working correctly, in order to surface the most relevant content and features to you. <strong>While our computer systems do a pretty good job, sometimes a limited amount of human review is simply unavoidable in order to make sure everything is working exactly as it should.</strong> <em>(emphasis ours)</em></p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A little further down they asked the question:</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Who will be able to access my data?</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We keep <strong>the list of Evernote employees who have access to user data as small as possible.</strong> <em>(emphasis ours)</em></p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was in 2016 and they <a href="https://evernote.com/blog/evernote-revisits-privacy-policy-change" target="_blank" class="c_accent">quickly</a> <a href="https://evernote.com/blog/evernotes-action-plan-for-privacy/" target="_blank" class="c_accent">reverted</a> this privacy policy update but the damage was done:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/joe-hill-x-post.CDTyMdDK.webp 602w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/joe-hill-x-post.DxucmipT.png 602w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/joe-hill-x-post.DxucmipT.png" srcset="https://blog.notesnook.com/assets/static/joe-hill-x-post.DxucmipT.png 602w" sizes="100vw" alt="Loved using @evernote, sorry their new privacy policy mean I'll be moving my notes somewhere else. They ain't for engineers to pick thru." style="max-width:602px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">@joehill on Twitter/X</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Even though this is 9 years old, it is a perfect example of a company abusing user privacy. You have to give Evernote props for listening to their users and being honest. Most companies aren't but that doesn't make the privacy situation any better. A privacy policy is just a promise, after all — it doesn't have any built-in limits to prevent the vendor from accessing your private data.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To put it simply, if I hand you a notebook for safekeeping, you'll be able to open &amp; read it anytime. You might promise not to but I have no control over it. Even if you opened &amp; read it, I'd have no way of knowing or preventing you.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Evernote works similarly: you give them your notes for safe keeping but there's nothing preventing them from reading those notes except a <a href="https://evernote.com/privacy/policy" target="_blank" class="c_accent">promise with conditions</a>:</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">no one at Evernote <strong>can view it unless you expressly give us permission or it’s necessary to comply with our legal obligations</strong></p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">"If necessary to comply with our legal obligations" is a slippery slope. It basically gives a company free reign to do whatever it wants with your data under a legal pretence. But it also means the company (and in turn its employees) <em>can</em> open &amp; access your notes <em>at anytime</em> as long as their is a legal basis.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A recent example of this is Telegram September 2024 privacy policy update which allowed them to disclose users’ IP addresses and phone numbers to law enforcement agencies internationally for any user arbitrarily. This <a href="https://github.com/Te-k/telegram-transparency" target="_blank" class="c_accent">impacted hundreds of thousands of users worldwide</a>, users who probably started using Telegram due to its "promises", users who were caught unaware by a company's policy changes.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There is nothing wrong with complying with legal requests but all laws are not equally good. Governments change, policies shift, the legal becomes illegal, something you wrote or said years ago could be used against you today, and in it all, you remain powerless because your data is not in your control.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In contrast, if you look at end-to-end encrypted systems, your data is encrypted <em>before</em> you give it over to the vendor much like locking away your notebook in a secure box before giving it to a friend for safe keeping. Even if your friend wanted to, he'd have no way to open the box to see what's inside. <strong>Zero compromise. Complete control.</strong></p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">How far can you trust privacy policies?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In 2024, <a href="https://medium.com/data-science/consent-in-training-ai-75a377f32f65" target="_blank" class="c_accent">LinkedIn was caught abusing users' data for training AI</a> without their consent even though their privacy policy had no mention of this. A <a href="https://www.pewresearch.org/internet/2019/11/15/americans-attitudes-and-experiences-with-privacy-policies-and-laws/" target="_blank" class="c_accent">survey</a> conducted in 2019 in America concluded that 78% people almost never read privacy policies, and those that do, just glance over it.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>How can you trust in a promise that you don't even read?</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Technically speaking, if a company violated their privacy policy the users wouldn't know about it until after the damage was done. For example, if Evernote secretly trained an ML model on your notes, there would be zero public proof to hold against them. This has happened countless times where privacy policy updates were made only after users' privacy had been violated:</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">In 2010, Google launched their social media platform, Google Buzz, and automatically enrolled all Gmail users making their contact data publicly visible—<a href="https://phys.org/news/2011-03-google-privacy-settlement-ftc.html?utm_source=chatgpt.com" target="_blank" class="c_accent">without user consent or a prior privacy policy update.</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">In 2013, <a href="https://natlawreview.com/article/privacy-monitoring-activity-federal-trade-commission?utm_source=chatgpt.com" target="_blank" class="c_accent">Path made a settlement with FTC</a> for violating users' privacy by secretly uploading their mobile address books.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">In 2017, Vizio embedded tracking software in its smart TVs that monitored user viewing habits and device metadata (e.g. Wi-Fi networks and demographic segments) and transmitted this to third parties—despite general privacy policy statements that did not clearly disclose these intrusive practices.</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There are countless examples like these where the company said one thing in its privacy policy but did the complete opposite. Privacy policies are just that: policies. We, as users, trust the companies will keep their word but there are no built-in limits to prevent them from <em>not</em> doing that.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">At the end, the choice is yours: <strong>do you want privacy by promise or privacy by design?</strong></p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">End-to-end encryption requires no trust</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In end-to-end encryption, you own the keys, the door, <em>and</em> the house. <em>You</em> decide who enters — not some company policy that can change without notice. The <a href="https://evernote.com/privacy/policy" target="_blank" class="c_accent">new Evernote privacy policy mentions</a>:</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To help refine or improve the technology, we <strong>may ask you for permission to review portions of your Content</strong>.</p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">How is this "permission" acquired? A toggle in the app? What does the toggle actually do? Does it hold the key to your data? No. Does it have control over who can access your data? No. It just updates a value in a database that has nothing to do with your data. What happens if your personal notes get "reviewed" <em>without</em> your permission by mistake? This is what end-to-end encryption was designed to prevent.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">"Encrypting everything is just not realistic"</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A lot of companies run away from end-to-end encryption because it's hard. It's hard to do everything on users' devices. It's hard to make things cross-platform. It's hard to make a good search. It's hard to keep everything performant.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It's hard but not impossible.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you go through <a href="https://evernote.com/privacy/transparency-report" target="_blank" class="c_accent">Evernote's transparency reports</a>, you will notice multiple instances where Evernote responded to requests with user data. They do not mention whether that includes user notes but they are legally bound to respond to such requests with as much data as they can within the limits of the law.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The question is: why do they have the power to "provide" your data, with or without your consent, to <em>anyone</em>? Shouldn't you be in control of who gets to access your data? <strong>Is it even your data if you have no control over who can read, access, and monitor it?</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Evernote is not alone in this. All companies that do not end-to-end encrypt user data have to hand over your private data if pressured under the law. They have no choice in the matter. By giving them power to host your data, you also give them the control to share whatever they want with whoever they want.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">What about companies that end-to-end encrypt everything? Aren't they also legally required to respond? They are but everything is encrypted so there's not much anyone can do even if they have the raw encrypted blobs.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">"It's just notes"</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Why do you put a lock on your phone? Isn't it "just a phone"? Why not let everyone access everything in it freely? Because it's private, it's personal, and it doesn't matter what you have in there — what matters is that it's yours.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Caring about privacy does not make you a criminal — it just makes you human.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Conclusion</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Don't depend on privacy policies to protect your privacy. Unless your data is encrypted before it leaves your device, someone, somewhere, always has the keys and the access. If this concerns you (as it should) and you want to take back control over your data then here are a few resources you can look into:</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://privacyguides.org" target="_blank" class="c_accent">Privacy Guides</a> - the best way to start taking back control over your digital life. Includes recommendations for alternatives to non-private tools like browsers, operating systems, email clients, note taking apps etc. Their community is absolutely awesome and will help you start your privacy journey. I recommend checking out their <a href="https://www.privacyguides.org/en/basics/why-privacy-matters/" target="_blank" class="c_accent">Why Privacy Matters</a> article.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://techlore.tech" target="_blank" class="c_accent">Techlore</a> - a wonderful resource of videos, articles, tools, alternatives to help you get started with privacy.</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There are many other resources you can follow, as long as you keep one thing in mind: <strong>your privacy matters.</strong></p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Privacy</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.JRRbeaCM.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.2.0]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.2.0</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.2.0</guid>
            <pubDate>Tue, 17 Jun 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[Archive, new improved search UI/UX with sorting & highlighting, integrated importer, and much more!]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.2.0 introduces Archive, a new and improved search experience with sorting &amp; highlighting, integrated importer, and much more!</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">New search experience</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Search has always been <em>horrible</em> in Notesnook. Well, that is changing now. In this release, we have made massive improvements to the way search works, including:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Token by token search highlighting.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Improved relevance based sorting.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Sort search results by title, date edited etc.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Jump directly to a specific search result within a note.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">No more false positives.</li>
</ol>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/new-search.CPUWD5yq.webp 640w, https://blog.notesnook.com/assets/static/new-search.ujvx2-Fk.webp 1024w, https://blog.notesnook.com/assets/static/new-search.Dq2WnwO8.webp 1600w, https://blog.notesnook.com/assets/static/new-search.ulTRL2yz.webp 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/new-search.LVylwMGy.png 640w, https://blog.notesnook.com/assets/static/new-search.DpncZROV.png 1024w, https://blog.notesnook.com/assets/static/new-search.Kxs6sulQ.png 1600w, https://blog.notesnook.com/assets/static/new-search.CDNwAPFE.png 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/new-search.LVylwMGy.png" srcset="https://blog.notesnook.com/assets/static/new-search.LVylwMGy.png 640w, https://blog.notesnook.com/assets/static/new-search.DpncZROV.png 1024w, https://blog.notesnook.com/assets/static/new-search.Kxs6sulQ.png 1600w, https://blog.notesnook.com/assets/static/new-search.CDNwAPFE.png 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="New search experience" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">New search experience</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">&nbsp;</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Archive</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I know, I know, this is pretty basic and it shouldn't have taken us this long but here we are. Archive. Finally. It works exactly how you'd expect it to: notes that you archive will only be visible and searchable from within the archive.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Integrated Importer</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are moving the Importer directly into the web/desktop app making for a much better migration experience. No more back and forth between multiple pages. Just drag/drop your Evernote (or some other notes app) archive and it'll directly be imported into the app.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/integrated-importer.C6wHDyl4.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/integrated-importer.BhS90Z6c.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/integrated-importer.BhS90Z6c.png" srcset="https://blog.notesnook.com/assets/static/integrated-importer.BhS90Z6c.png 640w" sizes="100vw" alt="Integrated importer" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Integrated importer</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can <a href="https://help.notesnook.com/importing-notes" target="_blank" class="c_accent">read our guide</a> on how to how to use the new importer.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Set any tag as default</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now set any specific tag as default and it will be applied automatically for all new notes (works just like default notebook). Just right click (or long press on mobile) on a tag and select "Set as default".</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and minor improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As with all our releases, we've fixed a bunch of bugs and improved the overall user experience. Thanks to our beta users you'll find this release much more stable and performant.</p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.1.1...v3.2.0" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.1.0]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.1.0</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.1.0</guid>
            <pubDate>Tue, 22 Apr 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[New sidebar UI with tabs for notebooks & tags, tree view for notebooks, and many other improvements.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.1.0 introduces a new sidebar UI with tabs for notebooks &amp; tags, tree view for notebooks, and many other improvements.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">New sidebar UI</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A lot of users have been frustrated with the way notebooks and tags functioned making it cumbersome to organize &amp; find notes. While we could just slap a tree view on top of the existing UI, move everything into the sidebar and call it a day. We did not want to clutter the sidebar by moving everything into a flat list top-to-bottom. That would make finding things even harder even though it'd be more familiar.</p>
<div class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/new-sidebar.xbC8XA4g.webp 222w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/new-sidebar.DpCLNnT6.png 222w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/new-sidebar.DpCLNnT6.png" srcset="https://blog.notesnook.com/assets/static/new-sidebar.DpCLNnT6.png 222w" sizes="100vw" alt="Themed title bar in Forevernote Dark theme." style="max-width:222px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">New sidebar UI with colors &amp; shortcuts</figcaption></figure><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/new-sidebar-tags.Cjvntkzw.webp 222w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/new-sidebar-tags.C_mdYLuC.png 222w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/new-sidebar-tags.C_mdYLuC.png" srcset="https://blog.notesnook.com/assets/static/new-sidebar-tags.C_mdYLuC.png 222w" sizes="100vw" alt="Tags in the new sidebar" style="max-width:222px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Tags in the new sidebar</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">&nbsp;</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Moving notebooks &amp; tags into their dedicated tabs makes many things simpler. For example, you can now easily filter notebooks or tags without having to scroll through a long list. Additionally, the tree view for notebooks allows you to quickly navigate through your notes in one place.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Tree view for notebooks</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With a dedicated tree view for notebooks, we aim to solve 2 things:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Seamless navigation</strong>: The tree view allows you to quickly navigate through your notebooks and their contents without any back &amp; forth.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Effortless organization</strong>: Organizing notes is just a simple drag-and-drop operation.</li>
</ol>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/new-sidebar-notebooks.VorRzIbM.webp 222w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/new-sidebar-notebooks.n5rBFU05.png 222w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/new-sidebar-notebooks.n5rBFU05.png" srcset="https://blog.notesnook.com/assets/static/new-sidebar-notebooks.n5rBFU05.png 222w" sizes="100vw" alt="Notebooks in the new sidebar" style="max-width:222px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Notebooks in the new sidebar</figcaption></figure>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Set any notebook, tag or color as homepage</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Previously, you could only set the homepage to the notebooks, tags or other pages. Now, you can set the homepage to any specific tag, color, or notebook. Just right click (or long press on mobile) and select "Set as homepage".</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and minor improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As with all our releases, we've fixed a bunch of bugs and improved the overall user experience. Thanks to our beta users you'll find this release much more stable and performant.</p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.32...v3.1.0" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.29]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.29</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.29</guid>
            <pubDate>Sat, 01 Mar 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 5+ bugs via 26 commits. Introducing Notesnook Beta release track, add keyboard shortcut for opening settings, and more!]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.29 fixes 5+ bugs via 26 commits. Introducing Notesnook Beta release track, add keyboard shortcut for opening settings, and more!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.27" target="_blank" class="c_accent">v3.0.27</a> fixes 10+ bugs via 53 commits. Command palette, quick open, paste as markdown, wrapped multiline title in web/desktop app, and more!</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.26" target="_blank" class="c_accent">v3.0.26</a> fixes 10+ bugs via 39 commits. Fixed 2 security vulnerabilities, new keyboard shortcuts for tabs, and more!</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.25" target="_blank" class="c_accent">v3.0.25</a> fixes 10+ bugs via 88 commits. Improved tabs with history, navigation &amp; other goodies!</li>
</ul>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Notesnook Beta Release Track</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are excited to introduce the Notesnook Beta release track. This track will allow you to try out the latest features and improvements before they are released to the stable version. You can switch to the Beta release track by going to the Settings &gt; About and switching to the Beta release track.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Note: you can switch back to the stable release track at any time without losing any data.</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you like to live at the edge, we'd appreciate if you can join the beta and help test out new features and improvements. Your feedback will help us make Notesnook even better!</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Add keyboard shortcut for opening settings</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Press <code style="color:#079315">Ctrl + ,</code> (Windows/Linux) or <code style="color:#085b70">Cmd + ,</code> (Mac) to open the settings dialog.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7701" target="_blank" class="c_accent">#7701</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and minor improvements</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph inset-s_2 m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Adjust toast position by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7665" target="_blank" class="c_accent">#7665</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Open internal links in new tab using middle mouse click by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7688" target="_blank" class="c_accent">#7688</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix tags and notebooks search</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix single &amp; double spaced paragraph margins in editor &amp; callouts</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix gap between title and editor on entering focus mode</li>
</ol>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.28...v3.0.29" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.27]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.27</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.27</guid>
            <pubDate>Mon, 24 Feb 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 10+ bugs via 53 commits. Command palette, quick open, paste as markdown, wrapped multiline title in web/desktop app, and more!]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.27 fixes 10+ bugs via 53 commits. Command palette, quick open, paste as markdown, wrapped multiline title in web/desktop app, and more!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.26" target="_blank" class="c_accent"><code style="color:#ac01c6">v3.0.26</code></a> fixes 10+ bugs via 39 commits. Fixed 2 security vulnerabilities, new keyboard shortcuts for tabs, and more!</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.25" target="_blank" class="c_accent"><code style="color:#c506e2">v3.0.25</code></a> fixes 10+ bugs via 88 commits. Improved tabs with history, navigation &amp; other goodies!</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.24" target="_blank" class="c_accent"><code style="color:#e002e0">v3.0.24</code></a> fixes 20+ bugs via 65 commits. Better integrated titlebar, reminders &amp; note widgets on iOS/Android, and more!</li>
</ul>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Note: On the mobile app, the version containing these fixes &amp; improvements should be v3.0.33.</strong></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Command palette</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Introducing the command palette, a powerful tool that allows you to quickly access and execute various actions within Notesnook. With just a few keystrokes, you can perform tasks such as creating new notes, notebooks, navigating, and managing your tags.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Just press <code style="color:#0ea33d">Ctrl + k</code> (Windows/Linux) or <code style="color:#04076d">Cmd + k</code> (Mac) to open the command palette, and start executing away.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/command-palette.CndS-6lV.webp 640w, https://blog.notesnook.com/assets/static/command-palette.f2pdnuRC.webp 1024w, https://blog.notesnook.com/assets/static/command-palette.DflQPfOh.webp 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/command-palette.DKSAUiuz.png 640w, https://blog.notesnook.com/assets/static/command-palette.DLg10ZE9.png 1024w, https://blog.notesnook.com/assets/static/command-palette.BTzjKb20.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/command-palette.DKSAUiuz.png" srcset="https://blog.notesnook.com/assets/static/command-palette.DKSAUiuz.png 640w, https://blog.notesnook.com/assets/static/command-palette.DLg10ZE9.png 1024w, https://blog.notesnook.com/assets/static/command-palette.BTzjKb20.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Command palette" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Command palette</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7314" target="_blank" class="c_accent">#7314</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Quick open</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In addition to the command palette, you can also use the quick open feature to quickly access your notes, notebooks, tags &amp; reminders. Just press <code style="color:#9b0915">Ctrl + p</code> (Windows/Linux) or <code style="color:#9e0743">Cmd + p</code> (Mac) to access the quick open dialog.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/quick-open.2AwQ3_T3.webp 640w, https://blog.notesnook.com/assets/static/quick-open.BdIfX2n1.webp 1024w, https://blog.notesnook.com/assets/static/quick-open.DZbsGKoz.webp 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/quick-open.D_JNAy2-.png 640w, https://blog.notesnook.com/assets/static/quick-open.PGFaLbJT.png 1024w, https://blog.notesnook.com/assets/static/quick-open.bqrbKOy9.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/quick-open.D_JNAy2-.png" srcset="https://blog.notesnook.com/assets/static/quick-open.D_JNAy2-.png 640w, https://blog.notesnook.com/assets/static/quick-open.PGFaLbJT.png 1024w, https://blog.notesnook.com/assets/static/quick-open.bqrbKOy9.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Quick open" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Quick open</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7314" target="_blank" class="c_accent">#7314</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Paste as markdown</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is one of the most requested features, especially by users who prefer Markdown over Rich Text. Notesnook has always supported <a href="https://help.notesnook.com/rich-text-editor/markdown-notes-editing" target="_blank" class="c_accent">Markdown shortcuts</a> but now it also supports pasting Markdown content directly into notes.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Whenever you paste something into the editor, Notesnook will automatically detect if it's Markdown (based on some heuristics) and convert it to rich text. If you don't want this behavior, you can use <code style="color:#ef1799">Ctrl+Shift+V</code> (Windows/Linux) or <code style="color:#9902cc">Cmd+Option+Shift+V</code> (Mac) to paste as plain text.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a> in <a href="https://github.com/streetwriters/notesnook/pull/7608" target="_blank" class="c_accent">#7608</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Wrapped titles in web/desktop app</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The mobile version of Notesnook has always had wrapping for titles but now it's also available in the web/desktop app.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/wrapped-title.Cvo7yktP.webp 640w, https://blog.notesnook.com/assets/static/wrapped-title.bt9nftxa.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/wrapped-title.CriBcLc9.png 640w, https://blog.notesnook.com/assets/static/wrapped-title.CJ0AVqQh.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/wrapped-title.CriBcLc9.png" srcset="https://blog.notesnook.com/assets/static/wrapped-title.CriBcLc9.png 640w, https://blog.notesnook.com/assets/static/wrapped-title.CJ0AVqQh.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Wrapped title" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Wrapped title</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7290" target="_blank" class="c_accent">#7290</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and minor improvements</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix status bar disappearing on small/tablet-sized screens by <a href="https://github.com/luis-411" target="_blank" class="c_accent">@luis-411</a> in <a href="https://github.com/streetwriters/notesnook/pull/7542" target="_blank" class="c_accent">#7542</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix note title sync if note is opened in multiple tabs by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7636" target="_blank" class="c_accent">#7636</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix submenu positioning by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7632" target="_blank" class="c_accent">#7632</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix tags can not be added/removed from the editor by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7623" target="_blank" class="c_accent">#7623</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Add scroll margin &amp; threshold in editor to improve scrolling with arrow keys experience by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7292" target="_blank" class="c_accent">#7292</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Improve hr &amp; p spacing in editor by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7489" target="_blank" class="c_accent">#7489</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix pinned tabs allowing new notes to be opened in the same tab by <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a> and <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7457" target="_blank" class="c_accent">#7457</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix search in complex elements like tables, lists etc. by <a href="https://github.com/Waqar144" target="_blank" class="c_accent">@Waqar144</a> in <a href="https://github.com/streetwriters/notesnook/pull/7605" target="_blank" class="c_accent">#7605</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix youtube embeds rendering as black blocks by <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a> in <a href="https://github.com/streetwriters/notesnook/pull/7608" target="_blank" class="c_accent">#7608</a></li>
</ol>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.26...v3.0.27" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.26]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.26</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.26</guid>
            <pubDate>Fri, 14 Feb 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 10+ bugs via 39 commits. Fixed 2 security vulnerabilities, new keyboard shortcuts for tabs, and more!]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.26 fixes 10+ bugs via 39 commits. Fixed 2 security vulnerabilities, new keyboard shortcuts for tabs, and more!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.25" target="_blank" class="c_accent"><code style="color:#c506e2">v3.0.25</code></a> fixes 10+ bugs via 88 commits. Improved tabs with history, navigation &amp; other goodies!</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.24" target="_blank" class="c_accent"><code style="color:#e002e0">v3.0.24</code></a> fixes 20+ bugs via 65 commits. Better integrated titlebar, reminders &amp; note widgets on iOS/Android, and more!</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.23" target="_blank" class="c_accent"><code style="color:#ae0be5">v3.0.23</code></a> fixes 30+ bugs via 99 commits. Gift cards, search results sorted by date created, improved UX when auto save is disabled, and more.</li>
</ul>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Note: On the mobile app, the version containing these fixes &amp; improvements should be v3.0.32.</strong></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix 2 potential XSSs when pasting untrusted content into the editor</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Potential XSS when pasting/inserting an <code style="color:#e804e8">iframe</code> containing a <code style="color:#ed159a">javascript</code> link.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Potential XSS when pasting/inserting an <code style="color:#c1036c">svg</code> containing JavaScript (why do SVGs allow JS in the first place?).</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Mitigations include disallowing all execution of JS inside an SVG by rendering it in a sandboxed <code style="color:#e804e8">iframe</code>. While we cannot disallow JS execution in embeds (that would break all embeds like YouTube videos), we have disallowed access to the parent window to all <code style="color:#e804e8">iframe</code>s, again, by using a sandboxed <code style="color:#e804e8">iframe</code> and by disallowing embedding of <code style="color:#e2129d">javascript:</code> links.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">These vulnerabilities affect the following apps:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Web app - medium risk</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Mobile app - low risk since the editor runs in its own process completely isolated from the Notesnook app</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Desktop app - medium risk</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">These vulnerabilities cannot be used to steal or access your notes or any other data since everything is stored and accessed from an encrypted SQLite database which isn't globally accessible by any script. Your data would be 100% safe and isolated from such an attack. However, an attacker could still cause havoc and track your activities while using the app so it's recommended that you upgrade to v3.0.26 as soon as possible.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are not aware of any instance where this vulnerability was exploited in the wild. It is still recommended that you double check before pasting things from untrusted parts of the web.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Special thanks to <a href="https://twitter.com/@sksec_" target="_blank" class="c_accent">@sksec_</a> for responsibly reporting these vulnerabilities.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Keyboard shortcuts for tabs</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Yep, we are finally working on making Notesnook keyboard accessible. Better late than never, right? Anyway, we have added the following new keyboard shortcuts:</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#e80b76">Ctrl/Cmd+t</code> to open new tab.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#bf0b83">Ctrl/Cmd+n</code> to create a new note.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#bf00b2">Ctrl/Cmd+w</code> to close the active tab.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#d815b8">Ctrl/Cmd+Shift+W</code> to close all tabs.</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Some of these shortcuts might not work in the web app due to browsers disallowing overriding those shortcuts. In such cases, you can press an extra <code style="color:#8c1c0e">Shift</code> key to trigger the shortcut. For example, instead of <code style="color:#e00fd2">Ctrl/cmd+t</code> to open a new tab, you can press <code style="color:#dd0d94">Ctrl/cmd+Shift+T</code>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7322" target="_blank" class="c_accent">#7109</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Select programming language by pressing <code style="color:#b5140e">Enter</code> key</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now directly press the <code style="color:#b5140e">Enter</code> key to select the topmost language when changing the programming language of a code block. Pressing the <code style="color:#aa1205">Escape</code> key will now also close the language selector popup.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/KYash03" target="_blank" class="c_accent">@KYash03</a> in <a href="https://github.com/streetwriters/notesnook/pull/7484" target="_blank" class="c_accent">#7484</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix search queries containing special characters</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Special characters are recognized as column names by SQLite and need to be properly escaped if they are to be searched.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/luis-411" target="_blank" class="c_accent">@luis-411</a> in <a href="https://github.com/streetwriters/notesnook/pull/7418" target="_blank" class="c_accent">#7418</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Device specific settings are no longer reset on logout</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Previously, we were clearing everything in the local stores on logout which also cleared things like the current selected theme, font sizes etc. In this release, all device specific settings are ignored when logging out.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7423" target="_blank" class="c_accent">#7423</a> and <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7436" target="_blank" class="c_accent">#7436</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and minor improvements</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix status bar disappearing on small/tablet-sized screens by <a href="https://github.com/luis-411" target="_blank" class="c_accent">@luis-411</a> in <a href="https://github.com/streetwriters/notesnook/pull/7542" target="_blank" class="c_accent">#7542</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Hide undo &amp; redo buttons for readonly notes by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7541" target="_blank" class="c_accent">#7541</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix app icon appearing corrupt in some places on macOS by <a href="https://github.com/xa4hf8" target="_blank" class="c_accent">@xa4hf8</a> in <a href="https://github.com/streetwriters/notesnook/pull/7542" target="_blank" class="c_accent">#7542</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix various typos in code comments and other places by <a href="https://github.com/luzpaz" target="_blank" class="c_accent">@luzpaz</a> in <a href="https://github.com/streetwriters/notesnook/pull/7463" target="_blank" class="c_accent">#7463</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix subnotebook title not updating on navigate by <a href="https://github.com/luis-411" target="_blank" class="c_accent">@luis-411</a> in <a href="https://github.com/streetwriters/notesnook/pull/7286" target="_blank" class="c_accent">#7286</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix task list stats appearing as 0/0 on app reload by <a href="https://github.com/luis-411" target="_blank" class="c_accent">@luis-411</a> in <a href="https://github.com/streetwriters/notesnook/pull/7327" target="_blank" class="c_accent">#7327</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix collapsed pane expanding on app reload by <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a> in <a href="https://github.com/streetwriters/notesnook/pull/7449" target="_blank" class="c_accent">#7449</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix crash on app launch with new tabs by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7451" target="_blank" class="c_accent">#7451</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Show progress when taking backup and allow to hide backup dialog by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7452" target="_blank" class="c_accent">#7452</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix opening note from widget opens incorrect note by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7453" target="_blank" class="c_accent">#7453</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix unlocking note with biometrics by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7459" target="_blank" class="c_accent">#7459</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix file size is 0 errors when downloading attachments by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7458" target="_blank" class="c_accent">#7458</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix dialog calling onClose after pressing positive button by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7558" target="_blank" class="c_accent">#7558</a></li>
</ol>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.25...v3.0.26" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.25]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.25</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.25</guid>
            <pubDate>Mon, 03 Feb 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 10+ bugs via 88 commits. Improved tabs with history, navigation & other goodies!]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.25 fixes 10+ bugs via 88 commits. Improved tabs with history, navigation &amp; other goodies!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.24" target="_blank" class="c_accent"><code style="color:#e002e0">v3.0.24</code></a> fixes 20+ bugs via 65 commits. Better integrated titlebar, reminders &amp; note widgets on iOS/Android, and more!</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.23" target="_blank" class="c_accent"><code style="color:#ae0be5">v3.0.23</code></a> fixes 30+ bugs via 99 commits. Gift cards, search results sorted by date created, improved UX when auto save is disabled, and more.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.20" target="_blank" class="c_accent"><code style="color:#b213dd">v3.0.20</code></a> fixes 40+ bugs via 167 commits. Faster app startup, improved ToC, more accurate search, zoom in editor, and faster checkout.</li>
</ul>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">New and improved tabs</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have overhauled the whole logic behind tabs in this release. Here are some of the improvements:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Tab history</strong>: You can now go back and forth in the tab history using the back and forward buttons in the tab bar.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>No more preview tabs</strong>: We have removed the preview tabs feature. Now, when you open a note, it will open in the same tab unless you right click and open it in a new tab explicitly.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Open a note in multiple tabs</strong>: You can now open a single note in multiple tabs (a precursor to side-by-side note editing). Changes in one tab will automatically sync to all other tabs.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">These changes will significantly improve the tab experience in Notesnook. You'll no longer have a 100 tabs open when you're just trying to find that one note. 🎉</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2"><code style="color:#b015ed">/nowz</code> and <code style="color:#d11488">/timestampz</code> editor shortcuts</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have added two new editor shortcuts to make your life easier:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong><code style="color:#b015ed">/nowz</code></strong>: This will insert the current date and time in the editor alongwith the timezone.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong><code style="color:#d11488">/timestampz</code></strong>: This will insert the current timestamp in the editor alongwith the timezone.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7270" target="_blank" class="c_accent">#7109</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and minor improvements</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix sidebar collapsing when resizing the window by <a href="https://github.com/luis-411" target="_blank" class="c_accent">@luis-411</a> in <a href="https://github.com/streetwriters/notesnook/pull/7377" target="_blank" class="c_accent">#7377</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix hover styling in toolbar color buttons by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7421" target="_blank" class="c_accent">#7421</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix various android widget bugs by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7416" target="_blank" class="c_accent">#7416</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix attaching a file in new note editor by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7422" target="_blank" class="c_accent">#7422</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Close sheet on backup completed by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7424" target="_blank" class="c_accent">#7424</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix release notes rendering by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7435" target="_blank" class="c_accent">#7435</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix <code style="color:#ed046d">object is not iterable</code> error by <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a> in <a href="https://github.com/streetwriters/notesnook/pull/7433" target="_blank" class="c_accent">#7433</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix empty notes not included in exports by <a href="https://github.com/thecodrr" target="_blank" class="c_accent">@thecodrr</a> in <a href="https://github.com/streetwriters/notesnook/pull/7433" target="_blank" class="c_accent">#7433</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix search not resetting when closed by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7415" target="_blank" class="c_accent">#7415</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Show toast when changing some settings on mobile by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7376" target="_blank" class="c_accent">#7376</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Add a link popup now has proper labels for fields by <a href="https://github.com/luis-411" target="_blank" class="c_accent">@luis-411</a> in <a href="https://github.com/streetwriters/notesnook/pull/7097" target="_blank" class="c_accent">#7097</a></li>
</ol>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.24...v3.0.25" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.24]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.24</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.24</guid>
            <pubDate>Tue, 21 Jan 2025 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 20+ bugs via 65 commits. New integrated titlebar, new reminders & note widgets on iOS/Android, and more!]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.24 fixes 20+ bugs via 65 commits. Better integrated titlebar, reminders &amp; note widgets on iOS/Android, and more!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.23" target="_blank" class="c_accent"><code style="color:#ae0be5">v3.0.23</code></a> fixes 30+ bugs via 99 commits. Gift cards, search results sorted by date created, improved UX when auto save is disabled, and more.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.20" target="_blank" class="c_accent"><code style="color:#b213dd">v3.0.20</code></a> fixes 40+ bugs via 167 commits. Faster app startup, improved ToC, more accurate search, zoom in editor, and faster checkout.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.18" target="_blank" class="c_accent"><code style="color:#cb13d8">v3.0.18</code></a> fixes 40+ bugs via 131 commits. Improved search query handling, support for configuring server urls from login screen, importing nested notebooks/folders from other apps, and more.</li>
</ul>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Better integrated titlebar on Desktop</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The app UI now integrates better with the titlebar taking much less space and looking more native. This change also unifies the titlebar across web &amp; desktop apps allowing for better themability.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/integrated-titlebar.5YaKIFyc.webp 640w, https://blog.notesnook.com/assets/static/integrated-titlebar.CCZ99mSA.webp 1024w, https://blog.notesnook.com/assets/static/integrated-titlebar.3Vm3CVO4.webp 1600w, https://blog.notesnook.com/assets/static/integrated-titlebar.C-wZwwHM.webp 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/integrated-titlebar.EM2SESx5.png 640w, https://blog.notesnook.com/assets/static/integrated-titlebar.CKKXJsh9.png 1024w, https://blog.notesnook.com/assets/static/integrated-titlebar.Bn0ldm1w.png 1600w, https://blog.notesnook.com/assets/static/integrated-titlebar.ClshYFFB.png 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/integrated-titlebar.EM2SESx5.png" srcset="https://blog.notesnook.com/assets/static/integrated-titlebar.EM2SESx5.png 640w, https://blog.notesnook.com/assets/static/integrated-titlebar.CKKXJsh9.png 1024w, https://blog.notesnook.com/assets/static/integrated-titlebar.Bn0ldm1w.png 1600w, https://blog.notesnook.com/assets/static/integrated-titlebar.ClshYFFB.png 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Better integrated titlebar on desktop" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Better integrated titlebar on desktop</figcaption></figure>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">New widgets on Android/iOS</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Reminders widget</strong>: You can now see your reminders on the home screen. This widget will show you all your active reminders, and also allow you to add a new reminder.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Note widget</strong>: Pin specific notes to your home screen. This widget will show you the pinned note's title and a preview of the content.</li>
</ol>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/widgets.CclNQ6Do.webp 400w, https://blog.notesnook.com/assets/static/widgets.DqTKDPmO.webp 600w, https://blog.notesnook.com/assets/static/widgets.C5XPoNs-.webp 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/widgets.BedKGR6g.png 400w, https://blog.notesnook.com/assets/static/widgets.CqgseDH1.png 600w, https://blog.notesnook.com/assets/static/widgets.BexnvNLc.png 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/widgets.BedKGR6g.png" srcset="https://blog.notesnook.com/assets/static/widgets.BedKGR6g.png 400w, https://blog.notesnook.com/assets/static/widgets.CqgseDH1.png 600w, https://blog.notesnook.com/assets/static/widgets.BexnvNLc.png 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Preview of the new widgets on Android/iOS" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Preview of the new widgets on Android/iOS</figcaption></figure>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Lock app button in status bar</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you have app lock enabled, you can now quickly lock the app from the status bar.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7333" target="_blank" class="c_accent">#7333</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Markdown shortcut for adding checklist</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now add a checklist by typing <code style="color:#d80ac7">- [ ]</code>. Typing <code style="color:#bc01ad">- </code> will create a bullet list as usual and if you type <code style="color:#6f0d99">[ ]</code> in the bullet list, it'll automatically convert the bullet list into a checklist.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7087" target="_blank" class="c_accent">#7087</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Keyboard shortcuts for next/previous tab</h3>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#bc0de8">Ctrl/Cmd + Alt/Option + Left Arrow</code> to go to the previous tab</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ed00d9">Ctrl/Cmd + Alt/Option + Right Arrow</code> to go to the next tab</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7109" target="_blank" class="c_accent">#7109</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Save image compression upload setting</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now disable the "Image compression" dialog shown whenever you paste/drop an image in the editor from Settings &gt; Behavior. This setting includes the following options:</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Enable</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Disable</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Ask me every time (default)</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7089" target="_blank" class="c_accent">#7089</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and minor improvements</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix casing in legal &amp; notebooks strings by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7142" target="_blank" class="c_accent">#7142</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix status bar size changing by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7143" target="_blank" class="c_accent">#7143</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix vault unlock status not shown when a note was unlocked by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7172" target="_blank" class="c_accent">#7172</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Replace <code style="color:#600d9b">react-resizable-panels</code> with custom implementation by @thecodrr in <a href="https://github.com/streetwriters/notesnook/pull/6829" target="_blank" class="c_accent">#6829</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix scroll jumping in realtime sync by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7247" target="_blank" class="c_accent">#7247</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix image file upload by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7246" target="_blank" class="c_accent">#7246</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix trash interval picker text showing NaN by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7319" target="_blank" class="c_accent">#7319</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix edit profile name dialog by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7338" target="_blank" class="c_accent">#7338</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix referencedIn casing by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7334" target="_blank" class="c_accent">#7334</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Render setting item descriptions as markdown by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7343" target="_blank" class="c_accent">#7343</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix follow casing by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7342" target="_blank" class="c_accent">#7342</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix <code style="color:#0a2a6b">DATE_TIME_STRIP_REGEX</code> by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7325" target="_blank" class="c_accent">#7325</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix clamped image size is larger than editor's width by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7324" target="_blank" class="c_accent">#7324</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix selecting image from gallery when applock is on by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7332" target="_blank" class="c_accent">#7332</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix material menu by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7331" target="_blank" class="c_accent">#7331</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix image previews not loading on ios by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7323" target="_blank" class="c_accent">#7323</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Always load note content in editor by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7320" target="_blank" class="c_accent">#7320</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix search strings by @luis-411 in <a href="https://github.com/streetwriters/notesnook/pull/7302" target="_blank" class="c_accent">#7302</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix reveal in list by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7297" target="_blank" class="c_accent">#7297</a></li>
</ol>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.23...v3.0.24" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.23]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.23</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.23</guid>
            <pubDate>Tue, 24 Dec 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 30+ bugs via 99 commits. Gift cards, search results sorted by date created, improved UX when auto save is disabled, and more.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.23 fixes 30+ bugs via 99 commits. Gift cards, search results sorted by date created, improved UX when auto save is disabled, and more.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#b213dd">v3.0.20</code> fixes 40+ bugs via 167 commits. Faster app startup, improved ToC, more accurate search, zoom in editor, and faster checkout.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#cb13d8">v3.0.18</code> fixes 40+ bugs via 131 commits. Improved search query handling, support for configuring server urls from login screen, importing nested notebooks/folders from other apps, and more.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#cc0081">v3.0.17</code> fixes 4 bugs via 11 commits. Fixed zero notebooks after adding 20+ notebooks on a Basic account, fixed multiple updates running simultaneously in desktop app, and fixed tags not assigned to web clips.</li>
</ul>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Search results sorted by date created</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Search results are now sorted by date created instead of relevance which was, to be frank, absolutely horrible. This change should make search results much more useful until we add support for sorting by other fields.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Gift cards</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now buy Notesnook Pro gift cards for your friends and family so they can enjoy better privacy too! Learn more about this in <a href="https://blog.notesnook.com/introducing-notesnook-gift-cards" target="_blank" class="c_accent">the blog post</a>.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Improved UX when auto save is disabled</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">By default, Notesnook disables auto saving when the note size exceeds 100K words but until now, there was almost no indication that auto save was disabled. Now, we have added multiple fail safes to prevent accidental data loss when auto save is disabled including:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">A warning prompt when auto save is disabled.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Saving the note automatically if its closed.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Saving the note automatically if the app moves to background.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Saving the note automatically when switching between notes.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This will ensure that you never lose your data even if auto save is disabled.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong><a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6849" target="_blank" class="c_accent">#6818</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes and minor improvements</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix attachment downloading on mobile by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/6971" target="_blank" class="c_accent">#6971</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Always show desktop backup directory path (#6867) by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/6953" target="_blank" class="c_accent">#6953</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix inconsistent zoom amount when adjusting with scroll wheel by @Leviob in <a href="https://github.com/streetwriters/notesnook/pull/6990" target="_blank" class="c_accent">#6990</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix zoom value showing value upto 9 decimal points by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7009" target="_blank" class="c_accent">#7009</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Hide spin buttons in mfa code input which were overlapping with other buttons by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7048" target="_blank" class="c_accent">#7048</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Disable spellcheck for inline code by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7036" target="_blank" class="c_accent">#7036</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Hide edit button when in read only mode by <a href="https://github.com/luis-411" target="_blank" class="c_accent">@luis-411</a> in <a href="https://github.com/streetwriters/notesnook/pull/7018" target="_blank" class="c_accent">#7018</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix ObjectId length is sometimes less than 24 characters by <a href="https://github.com/dyw770" target="_blank" class="c_accent">@dyw770</a> in <a href="https://github.com/streetwriters/notesnook/pull/6994" target="_blank" class="c_accent">#6994</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Handle errors in <code style="color:#617f06">parseInternalLink</code> by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7065" target="_blank" class="c_accent">#7065</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix tags not showing in locked note by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7061" target="_blank" class="c_accent">#7061</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix links in report issue dialog by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7071" target="_blank" class="c_accent">#7071</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Prevent creation of multiple empty notes by <a href="https://github.com/luis-411" target="_blank" class="c_accent">@luis-411</a> in <a href="https://github.com/streetwriters/notesnook/pull/6922" target="_blank" class="c_accent">#6922</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Make link color align with theme by <a href="https://github.com/luis-411" target="_blank" class="c_accent">@luis-411</a> in <a href="https://github.com/streetwriters/notesnook/pull/7077" target="_blank" class="c_accent">#7077</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Show account password label in delete vault dialog by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7085" target="_blank" class="c_accent">#7085</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix editor toolbar scroll styling by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> @thecodrr in <a href="https://github.com/streetwriters/notesnook/pull/7113" target="_blank" class="c_accent">#7113</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix mfa fallback string by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7123" target="_blank" class="c_accent">#7123</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix various localization issues &amp; crashes by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7008" target="_blank" class="c_accent">#7008</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix snooze time not rendered properly by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7080" target="_blank" class="c_accent">#7080</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix sharing files on Android by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7082" target="_blank" class="c_accent">#7082</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix custom proxy URL cannot be reset on mobile by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7094" target="_blank" class="c_accent">#7094</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix undo/redo buttons in editor by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7122" target="_blank" class="c_accent">#7122</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix task list stats not showing on opening a note by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7117" target="_blank" class="c_accent">#7117</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Disable pasting in task list when readonly mode is enabled by <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a> in <a href="https://github.com/streetwriters/notesnook/pull/7117" target="_blank" class="c_accent">#7117</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix multiple file sharing on android by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7138" target="_blank" class="c_accent">#7138</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix password hashing on iOS by <a href="https://github.com/ammarahm-ed" target="_blank" class="c_accent">@ammarahm-ed</a> in <a href="https://github.com/streetwriters/notesnook/pull/7093" target="_blank" class="c_accent">#7093</a></li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Thank you to <a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a>, <a href="https://github.com/luis-411" target="_blank" class="c_accent">@luis-411</a>, <a href="https://github.com/dyw770" target="_blank" class="c_accent">@dyw770</a> and all other contributors for making this release possible!</p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.22...v3.0.23" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Introducing Notesnook Gift Cards]]></title>
            <link>https://blog.notesnook.com/introducing-notesnook-gift-cards</link>
            <guid isPermaLink="false">https://blog.notesnook.com/introducing-notesnook-gift-cards</guid>
            <pubDate>Tue, 24 Dec 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Gift your loved ones a Notesnook Gift Card and let them enjoy  privacy-focused note-taking.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Cost is one of the biggest barriers to entry for new users into the privacy first world of digital tools. Even though the cost is worth it, not everyone wants to or can pay. With Notesnook we have tried to reduce this barrier as much as possible: we have a generous free tier, regional pricing, special plans for students and teachers, and now, gift cards.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">What are Notesnook Gift Cards?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Gift cards are a great way to introduce your friends and family to Notesnook without them having to worry about the cost. You can gift them a subscription plan of their choice and they can start using Notesnook without any hassle. It's a great way to show them that you care about their privacy and security.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Gift cards are non-renewable and can be used only once. They are available in 1 year, 3 year, and 5 year plans. You can purchase them from <a href="https://notesnook.com/giftcards" target="_blank" class="c_accent">our website</a> and send them to your friends and family. They can redeem the gift code in our app and start using Notesnook Pro right away.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Can I use the gift card to extend my subscription?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is currently not possible. Gift cards are meant to be gifted to others. If you want to purchase a subscription for yourself, you can do so from our app.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Is there an expiry date for the gift cards?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Yes, gift cards expire after 1 year of purchase. Make sure to redeem the gift card before it expires.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">How do I redeem the gift card?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have a detailed guide on how to redeem the gift card in our app. You can find it <a href="https://help.notesnook.com/gift-cards" target="_blank" class="c_accent">here</a>.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Free gift card giveaway</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To celebrate the launch of gift cards, we are giving away 10 free 1 year gift cards to our users. You don't have to do anything to participate. We will randomly select 10 users and send them a gift card.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are also giving away free 1 year gift cards to all our active Pro subscribers. If you have an active Notesnook Pro subscription, you should be receiving an email from us soon with the gift card code.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Upto 60% off on all gift cards</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are also offering a special discount on gift cards for a limited time. You can get upto 60% off on 1 year, 3 year, and 5 year gift cards. This is a limited time offer <strong>valid until January 2nd, 2025</strong>.</p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2"><a href="https://notesnook.com/giftcards" target="_blank" class="c_accent">Get your gift card now!</a></h3>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.20]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.20</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.20</guid>
            <pubDate>Tue, 19 Nov 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 40+ bugs via 167 commits. Faster app startup, improved ToC, more accurate search, zoom in editor, and faster checkout.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.20 fixes 40+ bugs via 167 commits. Faster app startup, improved ToC, more accurate search, zoom in editor, and faster checkout.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#cb13d8">v3.0.18</code> fixes 40+ bugs via 131 commits. Improved search query handling, support for configuring server urls from login screen, importing nested notebooks/folders from other apps, and more.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#cc0081">v3.0.17</code> fixes 4 bugs via 11 commits. Fixed zero notebooks after adding 20+ notebooks on a Basic account, fixed multiple updates running simultaneously in desktop app, and fixed tags not assigned to web clips.</li>
</ul>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Search improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In v3.0.18, we introduced a new search query transformer but it treated everything as a phrase by default i.e. <code style="color:#c1079f">hello world</code> would only return notes that contain <code style="color:#c1079f">hello world</code> and not <code style="color:#cc0a78">hello</code> or <code style="color:#a00ed1">world</code> or both. This was not the expected behavior and was causing a lot of confusion. This has been fixed now.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Secondly, we fixed search queries less than 3 characters returning no results in v3.0.17 but it lacked any kind of ranking or sorting. In this release, we have included a custom SQLite FTS5 tokenizer that tokenizes 1 and 2 character words separately making them easier to search. This also fixes issues where queries like <code style="color:#e5127f">top of the world</code> would return nothing because <code style="color:#b80fc1">of</code> is less than 3 characters in length.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">While we haven't tested this new tokenizer with CJK languages, it should work much better.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Faster app startup</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Web app startup has been improved by almost 5x from 3500ms to 700ms excluding network latency. This is a huge improvement and should make the app feel much snappier.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We removed 6-8 dependencies and replaced them with custom alternative that does the same thing in a more lightweight way. This has also reduced the app size by a few hundred kB.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">ToC improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Table of Contents (ToC) no longer covers note content and also preserves its open/closed state across app sessions. The ToC pane is now also resizable.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong><a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6818" target="_blank" class="c_accent">#6818</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Add support for configuring Monograph server URL</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now configure the app to use a custom Monograph URL instead of <code style="color:#e20bad">monogr.ph</code>.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Zoom in Editor</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now zoom in the editor using <code style="color:#ea00a4">Ctrl/Cmd</code> + mouse wheel. This is especially useful for users with accessibility issues.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong><a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6846" target="_blank" class="c_accent">#6846</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Vault unlocked indicator in status bar</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The status bar now shows if the vault is unlocked (clicking on the status bar button will lock it again) ensuring you don't accidentally leave the vault unlocked.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong><a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6830" target="_blank" class="c_accent">#6830</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixes</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed note title not focusing until clicked twice on Chrome (#6767) by <strong><a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6817" target="_blank" class="c_accent">#6817</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed restore notebooks over limit for free users (#6812) by <strong><a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6837" target="_blank" class="c_accent">#6837</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed attachment preview logic (#6822) by <strong><a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6843" target="_blank" class="c_accent">#6843</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed copy link button copying link text instead of link (#6642) by <strong><a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6845" target="_blank" class="c_accent">#6845</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed markdown link pasting (#6639) by <strong><a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6866" target="_blank" class="c_accent">#6866</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed backspace deleting formatting in list item by <strong><a href="https://github.com/01zulfi" target="_blank" class="c_accent">@01zulfi</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6878" target="_blank" class="c_accent">#6878</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed duplicate window controls showing on Linux desktop app by <strong><a href="https://github.com/alihamuh" target="_blank" class="c_accent">@alihamuh</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6844" target="_blank" class="c_accent">#6844</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Added confirm password check when changing app lock password by <strong><a href="https://github.com/alihamuh" target="_blank" class="c_accent">@alihamuh</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6808" target="_blank" class="c_accent">#6808</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed right side panel of settings is not scrollable on Chrome by <strong><a href="https://github.com/alihamuh" target="_blank" class="c_accent">@alihamuh</a></strong> in <a href="https://github.com/streetwriters/notesnook/pulls/6716" target="_blank" class="c_accent">#6716</a></li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other changes</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This release took a lot of time to get ready due to the massive changes required for localization. The app is now fully tranlatable i.e. once we setup a translation platform, we can start onboarding contributors to help translate the apps. Aside from that, here's a short list of changes that didn't make it to the main list:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Upgraded Electron to v31 (we stay a little behind to avoid any unintended issues).</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Added tests for the desktop app on all platforms</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Added benchmarks to ensure app startup remains fast</li>
</ol>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.18...v3.0.20" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.18]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.18</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.18</guid>
            <pubDate>Wed, 25 Sep 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 40+ bugs via 131 commits. Improved search query handling, support for configuring server urls from login screen, importing nested notebooks/folders from other apps, and more.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.18 fixes 40+ bugs via 131 commits. Improved search query handling, support for configuring server urls from login screen, importing nested notebooks/folders from other apps, and more.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#cc0081">v3.0.17</code> fixes 4 bugs via 11 commits. Fixed zero notebooks after adding 20+ notebooks on a Basic account, fixed multiple updates running simultaneously in desktop app, and fixed tags not assigned to web clips.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#c90aac">v3.0.15</code> &amp; <code style="color:#c60bb1">v3.0.16</code> fix a critical bug causing errors on sync &amp; other actions.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.14/" target="_blank" class="c_accent"><code style="color:#e204e2">v3.0.14</code></a> fixes 30 bugs via 124 commits. Added full offline mode, backups with attachments included, ability to change server URLs (self hosting yay!), and fixed a lot of bugs.</li>
</ul>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Improved search query handling</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook now has a query transformer that converts your query into something that SQLite FTS5 can easily parse. This means searches should now be massively improved across the board:</p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">1. Search query are now AND by default instead of OR</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Previously, if you searched for <code style="color:#c1079f">hello world</code>, it would return notes that contain either <code style="color:#cc0a78">hello</code> or <code style="color:#a00ed1">world</code>. Now, it will only return notes that contain both <code style="color:#cc0a78">hello</code> and <code style="color:#a00ed1">world</code>. This is the expected behavior on almost every search engine allowing you to narrow down search results as you type instead of widening them.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">2. Use capitalized AND/OR/NOT for more complex searches</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For example, you can search for <code style="color:#ea07ad">hello world NOT hello earth</code> to find notes that contain <code style="color:#c1079f">hello world</code> but not <code style="color:#9e02c9">hello earth</code>. Similarly, if you want to find notes that contain either <code style="color:#c1079f">hello world</code> or <code style="color:#9e02c9">hello earth</code>, you can search for <code style="color:#bc0d67">hello world OR hello earth</code>.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">3. Diacritics are now ignored when searching</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Diacritics are accents or marks added to letters. For example, <code style="color:#dd16b9">é</code> in <code style="color:#c113a1">café</code>. Previously, if you searched for <code style="color:#cc12cc">cafe</code>, it wouldn't return notes that contain <code style="color:#c113a1">café</code>. Now, it will return notes that contain either <code style="color:#cc12cc">cafe</code> or <code style="color:#c113a1">café</code>. Cool, right?</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Add support for configuring server URLs from login screen</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now configure the app to use your own server URLs right from the login/signup screen.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/change-server-urls-from-login-page.xqtnPc__.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/change-server-urls-from-login-page.DTJa8gt6.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/change-server-urls-from-login-page.DTJa8gt6.png" srcset="https://blog.notesnook.com/assets/static/change-server-urls-from-login-page.DTJa8gt6.png 640w" sizes="100vw" alt="Change server urls from login screen" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Change server urls from login screen.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The login screen also informs you about which instance you are using (the
official one or your own). This is so you can avoid accidentally creating an
account on the wrong instance.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Add support for importing nested notebooks/folders from other apps</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now migrate into Notesnook while preserving 100% of your notes organization. This includes when you are importing markdown or text files in nested folders.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Add delete key shortcut for deleting items</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you have selected multiple items (notes, tags etc.) you can quickly delete them by pressing the delete key. Easy peasy.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Add ctrl+k shortcut for inserting a new link</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">One of the most highly requested keyboard shortcuts, you can now press <code style="color:#dd06cb">Ctrl/Cmd + k</code> to open the <code style="color:#0b387c">Insert link</code> popup. This works automatically when you have some text selected and want to insert a link into it.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Add new dot separated (e.g. DD.MM.YYYY) date formats</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have added a couple of new dot separated date formats like <code style="color:#b109c4">DD.MM.YYYY</code>. These formats are used in various parts of the world (Russia, China etc.). To change the date format, go into Settings &gt; Behaviour.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Delete app data on uninstall (Windows only)</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When you uninstall the desktop app on Windows, it will now delete all app data. This includes your notes, settings, and other data. This is to ensure that your data is not left behind on your computer.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This only works on Windows which supports uninstall hooks.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix custom server URLs having no effect in production</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We enabled self hosting and ability to change server URLs in v3.0.17 but, accidentally, forgot to enable it in production. This has been fixed now.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Do not zip note without attachments on export</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Exporting a note that has no attachments will no longer create a ZIP archive of a single file. Hopefully, this will improve many users' workflows.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix attachment downloads stuck on error</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The app will now automatically skip over any errors that occur during bulk downloads of attachments. The errors will still be logged and shown on the screen but instead of getting stuck, the download process will continue downloading the next file.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Add integrity checks in various places for attachments</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have added integrity checks in various places like encryption, uploading to proactively detect corrupted uploads so you can take action. These checks mainly verify the file size before and after encryption &amp; upload to make sure there aren't any missing chunks.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Show progress when rechecking attachments in bulk</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">While it was possible to recheck attachments in bulk, there was no progress indicator. This has been fixed now. You'll see the progress in the status bar while the rechecks are taking place in the background.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix note title can be set to empty</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As much as we'd like to support untitled notes, titles are a fundamental part of Notesnook and are used in various critical places. This bug accidentally enabled untitled notes which caused issues like empty filenames on export &amp; publishing. This has been fixed now and affected notes should automatically get a new title when you update them.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix invalid filename when saving recovery key</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When you save your recovery key, the app creates a file named using your email address for easier identification. Due to this bug, the file was getting saved as <code style="color:#bf0988">[Object]-key.txt</code> instead of <code style="color:#c104e2">example@notesnook.com-key.txt</code>. This has been fixed now.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix <code style="color:#0c517f">Error in input stream</code> crash in web/desktop app</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This usually occurred after waking your PC/laptop from sleep while the app was running, or when after recovering from an Internet connection disruption. The error itself is harmless but it was causing the app to crash and display the "Something went wrong" screen.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Exit a link inside the editor by pressing space</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A massive QoL improvement for link junkies. If you insert a lot of links, it's extremely annoying when after inserting a link everything after it also becomes part of the link text. It was almost impossible to exit the link. We have fixed that in this release!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Just press the <code style="color:#930e15">Space</code> key and it'll automatically exit the link and you can continue typing without breaking a sweat.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Press Enter in link popup to insert link</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Another QoL improvement. This was such a basic expectation that I was almost embarrassed that someone created an issue for it. But, it's fixed now. You can press <code style="color:#b5140e">Enter</code> to insert the link instead of clicking the <code style="color:#912704">Insert</code> button.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix <code style="color:#ea15aa">found 0 byte sized chunk</code> error on attaching files in Firefox</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We use OPFS (Origin Private File System) for caching attachments in the browser which supports synchronous and very fast file system reads &amp; writes. As expected, all browsers don't work the same way though. On Chromium based browsers, it is okay to hold a <code style="color:#ad350d">read-only</code> handle to a file and write to it at the same time. Not so much on Firefox.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The underlying error looked something like this: <code style="color:#d613b5">No modification allowed</code> which meant, in order words, that we didn't have the permission to write or read to the file.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In v3.0.17 I added a fast path for getting file sizes used for integrity checks. The code looked something like this:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">readFileSize</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="2"></span>  directory<span class="token operator">:</span> FileSystemDirectoryHandle<span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span>  name<span class="token operator">:</span> <span class="token builtin">string</span>
<span class="ln-num" data-num="4"></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span>  <span class="token keyword">const</span> file <span class="token operator">=</span> <span class="token keyword">await</span> directory<span class="token punctuation">.</span><span class="token function">getFileHandle</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>  <span class="token keyword">const</span> handle <span class="token operator">=</span> <span class="token keyword">await</span> file<span class="token punctuation">.</span><span class="token function">createSyncAccessHandle</span><span class="token punctuation">(</span><span class="token punctuation">{</span> mode<span class="token operator">:</span> <span class="token string">'read-only'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>  <span class="token keyword">return</span> handle<span class="token punctuation">.</span><span class="token function">getSize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notice anything wrong? I wasn't closing the handle after reading the file size. Oops! The fix was simple:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> size <span class="token operator">=</span> handle<span class="token punctuation">.</span><span class="token function">getSize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span>handle<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span><span class="token keyword">return</span> size<span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Tested on Firefox and it worked! 🎉</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix error when downloading the same attachment on mobile</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The error looked something like this: <code style="color:#a609c1">Error: “3289114832a786d7_temp” couldn't be moved to “.cache” because an item with the same name already exists.</code> Apparently, the app wasn't overwriting the downloaded attachment causing the error. This has been fixed now.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix cannot jump to headings from TOC after copy pasting content</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Table of Contents in Notesnook works based on <code style="color:#d18008">data-block-id</code> in each heading. This <code style="color:#009623">block-id</code> is randomly generated for every node (except a few). However, not when it already exists which was the case when you copy pasted the same heading multiple times inside the note. When you tried to jump to the newly pasted headings, it would take you directly to the first one because the <code style="color:#d18008">data-block-id</code> for all headings was the same.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This has been fixed now. The editor automatically removes and regenerates the <code style="color:#009623">block-id</code> for each node when you paste anything.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix <code style="color:#e0b011">Cannot destructure property 'type' of 'e[0]' as it is undefined.</code> crash when searching</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was occurring because we weren't filtering out search results that had no note against them. This has been fixed now.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix issues with code block toggling</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Thanks to @seanwynwins for fixing this in <a href="https://github.com/streetwriters/notesnook/pull/6435" target="_blank" class="c_accent">#6435</a>.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><img alt="Before" src="https://blog.notesnook.com/media/notesnook-v3.0.18/toggle-codeblock-before.gif" class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><figcaption class="w_full ta_center font-style_italic fs_s c_info">Before</figcaption></figure><figure class="w_full m_0 d_flex flex-d_column ai_center"><img alt="After" src="https://blog.notesnook.com/media/notesnook-v3.0.18/toggle-codeblock-after.gif" class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><figcaption class="w_full ta_center font-style_italic fs_s c_info">After</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">&nbsp;</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix locking notes with empty content causing weird behavior</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When you tried to lock an empty note, it'd leave the whole note in an awkward state where the note said, "I am locked" but since it had no content, the app said "You are not locked". The problem worsened when you tried to <em>get out</em> of this state because now you couldn't unlock the note. Well done.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was fixed by <em>creating</em> a dummy content (out of thin air) when you try to lock an empty note.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix text rendering in bug report confirmation dialog</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Thanks to @Parthiv-M for fixing this in <a href="https://github.com/streetwriters/notesnook/pull/6573" target="_blank" class="c_accent">#6573</a>.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/bug-report-confirm-dialog.C_GuqVdx.webp 385w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/bug-report-confirm-dialog.DT0cgFkN.png 385w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/bug-report-confirm-dialog.DT0cgFkN.png" srcset="https://blog.notesnook.com/assets/static/bug-report-confirm-dialog.DT0cgFkN.png 385w" sizes="100vw" alt="The dialog now looks like this." style="max-width:385px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">The dialog now looks like this. Nice eh?</figcaption></figure>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix app crash after phone reboot on Android 14</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was caused because of the new changes made by Google in Android 14 that requires all apps that use Foreground Services to declare it in the manifest. Otherwise, the app would just crash.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fix app crash when app removed from Recents</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was caused because we were using MMKV for storing session data (i.e. which note you have opened) when you minimized the app. Initializing MMKV in the background was not super reliable so we have moved to using something more "native" for stuff like this.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>Note: This fix was released in v3.0.17.</em></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Improved attachments manager on mobile app</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The attachments manager now opens in full screen instead of in a small sheet at the bottom. We have also added a new "Errors" filter to quickly see attachments that have errors. The attachment manager also now shows in progress downloads at the top in a nice UI (especially useful when you turn on full offline mode and your attachments are downloading).</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Improved toast notifications UI on mobile</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The toast UI notifications has been improved (thanks to @ammarahm-ed) and all toasts are now shown on the bottom for better reachability.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/new-toast.zYAyiiia.webp 540w" sizes="100vw"><source type="image/jpg" srcset="https://blog.notesnook.com/assets/static/new-toast.CKrovJqi.jpg 540w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/new-toast.CKrovJqi.jpg" srcset="https://blog.notesnook.com/assets/static/new-toast.CKrovJqi.jpg 540w" sizes="100vw" alt="New toast UI" style="max-width:540px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">New toast UI</figcaption></figure>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other changes</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This release fixes a lot of long standing issues with the app. Here are some of the other changes:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Upgraded Electron to v30 (we stay a little behind to avoid any unintended issues).</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Lots of under the hood refactoring and improvements.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed status bar flicker on app launch on Android</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed markdown shortcuts toggle resetting on app launch</li>
</ol>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.17...v3.0.18" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.17]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.17</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.17</guid>
            <pubDate>Tue, 03 Sep 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 4 bugs via 11 commits. Fixed zero notebooks after adding 20+ notebooks on a Basic account, fixed multiple updates running simultaneously in desktop app, and fixed tags not assigned to web clips.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.17 fixes 4 bugs via 11 commits. Fixed zero notebooks after adding 20+ notebooks on a Basic account, fixed multiple updates running simultaneously in desktop app, and fixed tags not assigned to web clips.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#c90aac">v3.0.15</code> &amp; <code style="color:#c60bb1">v3.0.16</code> fix a critical bug causing errors on sync &amp; other actions.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.14/" target="_blank" class="c_accent"><code style="color:#e204e2">v3.0.14</code></a> fixes 30 bugs via 124 commits. Added full offline mode, backups with attachments included, ability to change server URLs (self hosting yay!), and fixed a lot of bugs.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.13/" target="_blank" class="c_accent"><code style="color:#ba03ba">v3.0.13</code></a> fixes 7 bugs via 8 commits. Fixed attachments flicker in note properties, pressing Enter in title now focuses the editor, and fixed crash on moving tabs.</li>
</ul>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed zero notebooks after adding 20+ notebooks on a Basic account</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was a bug introduced in v3.0.14. If you added more than 20 notebooks on a Basic account, all the notebooks would disappear.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed tags not assigned to web clips</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Tags were not being assigned to web clips due to the changes introduced in v3. The web clipper was sending the tags as-is instead of their ids causing the app to skip them. This has been fixed now.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed search queries less than 3 characters returned no results</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Ever since migrating to SQLite in v3, search queries less than 3 characters returned no results. This was due to a limitation in SQLite's FTS5 implementation. We have now added a workaround to allow searching for queries less than 3 characters.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Unfortunately, this workaround is not perfect and the results are not sorted but it is better than returning nothing.</p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.16...v3.0.17" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.14]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.14</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.14</guid>
            <pubDate>Wed, 28 Aug 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 30+ bugs via 124 commits. Added full offline mode, backups with attachments included, ability to change server URLs (self hosting yay!), and fixed a lot of bugs.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.14 fixes 30 bugs via 124 commits. Added full offline mode, backups with attachments included, ability to change server URLs (self hosting yay!), and fixed a lot of bugs.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.13/" target="_blank" class="c_accent"><code style="color:#ba03ba">v3.0.13</code></a> fixes 7 bugs via 8 commits. Fixed attachments flicker in note properties, pressing Enter in title now focuses the editor, and fixed crash on moving tabs.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.12/" target="_blank" class="c_accent"><code style="color:#ce0ece">v3.0.12</code></a> fixes 9 bugs via 16 commits. Faster attachment downloads, update availability indicator fixed on desktop app, and added support for clearing attachment cache.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.11/" target="_blank" class="c_accent"><code style="color:#db02c1">v3.0.11</code></a> fixes 10 bugs via 24 commits. Editor session data is now stored encrypted by default, fixed various editor related issues especially with pasting content, and other performance optimizations.</li>
</ul>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Okay, lots of things to unpack here. This was <em>almost</em> going to be v3.1.0 release but we are saving that for something a little more "fancy".</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Full offline mode</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook has always had "offline" mode in one way or another, but starting from v3.0.14, you can opt-in to downloading all your attachments upfront as opposed to on-demand. What's the difference, you ask?</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>On-demand</strong>: Attachments are downloaded when you open a note that has them. This is the default behavior and is good for saving bandwidth and storage.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Full offline</strong>: Attachments are downloaded on sync (and when you open the app) and are always available offline. This is good for when you often need to access your data without an Internet connection.</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>Should</em> you enable full offline mode on all your devices? We don't recommend this as it can quickly fill up your storage. Use it only on devices where you absolutely have to have all your data offline. If, however, you decide to enable it on all your devices, you can take advantage of the new "Backups with Attachments" feature to save up on bandwidth costs.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Backups with attachments</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is a feature that has been requested by a lot of users. You can now create "full" backups including all your attachments. This also automatically enables the "full offline mode".</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The minimum automatic interval for these backups is "Weekly" compared to "Daily" for normal backups. You can take as many full backups as you want manually, though.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The main reason for adding this feature was, of course, self hosting.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Self hosting</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Starting from v3.0.14, you can change server URLs from Settings &gt; Server. Self hosting is <strong>not</strong> stable yet so expect a lot of things not to work when connected to your own instance of Notesnook. However, we still included this change to allow people to easily early test this.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Faster sync on app startup</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was reported by a user on Reddit (even though I noticed this many times as well). The delay in first sync was caused by the app waiting on a lot of unnecessary things to finish before starting the sync.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Something you'll immediately notice in this release is that sync runs instantly on opening the app. We have also removed unncessary checks causing significant slowdown on each sync so you'll have a much smoother overall experience now.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Purge all opt-in telemetry related code</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We moved from <a href="https://blog.notesnook.com/telemetry-opt-in-vs-opt-out" target="_blank" class="c_accent">opt-out telemetry to opt-in telemetry</a> last year and it turned to be just as useless as expected. Very few people opted in and the data was next to useless, so we have decided to just get rid of this abomination. It doesn't belong in a privacy-first app anyway.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">No telemetry. No analytics. We are 100% blind on the client side. Not sure if that's a good thing but we'll see.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Limits on Basic plan are back</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In v3 we temporarily removed certain limits around notebooks, tags, and colors. We are adding them back with a slight difference: you now get 20 notebooks (instead of 3) in the basic plan. Everything else is exactly the same.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Prompt to take backup before logout</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook will now always prompt you to take a backup before logging out. The reason is simple: we don't want you to lose important data. For example, sometimes you are having connectivity issues, or the sync is broken for some other reason, and logging out erases all copies of your data. This is bad for everyone.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Additionally, the app will also warn you if you have unsynced changes on logout making sure you are fully aware.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I think this'll save you from a lot of headaches.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Faster image loading in editor</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was a constant complaint from a lot of users (especially on Reddit). Locally cached images should load much faster now (remote images are dependent on network speed so you probably won't notice as much of a difference there).</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Bug fixes</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As usual, this release fixes a lot of annoying bugs. I recommend checking out the commit history for a complete list.</p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.13...v3.0.14" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.13]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.13</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.13</guid>
            <pubDate>Sat, 27 Jul 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 7 bugs via 8 commits. Fixed attachments flicker in note properties, pressing Enter in title now focuses the editor, and fixed crash on moving tabs.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.13 fixes 7 bugs via 8 commits. Fixed attachments flicker in note properties, pressing Enter in title now focuses the editor, and fixed crash on moving tabs.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.12/" target="_blank" class="c_accent"><code style="color:#ce0ece">v3.0.12</code></a> fixes 9 bugs via 16 commits. Faster attachment downloads, update availability indicator fixed on desktop app, and added support for clearing attachment cache.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.11/" target="_blank" class="c_accent"><code style="color:#db02c1">v3.0.11</code></a> fixes 10 bugs via 24 commits. Editor session data is now stored encrypted by default, fixed various editor related issues especially with pasting content, and other performance optimizations.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.10/" target="_blank" class="c_accent"><code style="color:#dd028d">v3.0.10</code></a> fixes 5 bugs via 6 commits. Fixed weird UI glitches when scrolling in different lists, Pro popup is now shown only if there are images when pasting, and markdown shortcuts are working again.</li>
</ul>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed attachments flicker in note properties</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Flicker when scrolling the properties sidebar with a lot of attachments (50+) is now gone.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Pressing Enter now moves the focus to the Editor</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A minor UX improvement but you can now press Enter to directly focus the editor (instead of using the mouse or Tab).</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed crash on moving a temporary tab</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This crash was most probably occuring due to the migration we did in v3.0.11 from <code style="color:#e817e1">immer</code> to <code style="color:#dd16d3">mutative</code>. For some reason (I haven't investigated why yet), using <code style="color:#c101b1">splice</code> on an array adds an <code style="color:#cb0be0">undefined</code> item at the end of the error causing the crash. This has now been fixed with a workaround.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed error when setting a yearly reminder for January</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was a long standing bug from v2. In Notesnook, reminders work using a Task Scheduler that uses Cron expressions to schedule different tasks (e.g. update checks, reminders etc.). The error was occuring because in JavaScript, months are indexed from 0-11 while Cron expects 1-12. So an expression like this <code style="color:#db07ea">0 0 0 15 0 * *</code> causes a crash.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This has now been fixed.</p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.12...v3.0.13" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.12]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.12</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.12</guid>
            <pubDate>Mon, 22 Jul 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 9 bugs via 16 commits. Faster attachment downloads, update availability indicator fixed on desktop app, and added support for clearing attachment cache.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.12 fixes 9 bugs via 16 commits. Faster attachment downloads, update availability indicator fixed on desktop app, and added support for clearing attachment cache.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.11/" target="_blank" class="c_accent"><code style="color:#db02c1">v3.0.11</code></a> fixes 10 bugs via 24 commits. Editor session data is now stored encrypted by default, fixed various editor related issues especially with pasting content, and other performance optimizations.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.10/" target="_blank" class="c_accent"><code style="color:#dd028d">v3.0.10</code></a> fixes 5 bugs via 6 commits. Fixed weird UI glitches when scrolling in different lists, Pro popup is now shown only if there are images when pasting, and markdown shortcuts are working again.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.9/" target="_blank" class="c_accent"><code style="color:#a911c4">v3.0.9</code></a> fixes 15+ bugs via 28 commits. Fixed web app for mobile browsers, images now automatically get removed on paste for non-Pro users, and real-time editor sync now works again.</li>
</ul>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Faster attachment downloads</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Ever since we added support for attachments in Notesnook, we have been serving all the files from a single location (Germany). Naturally, this doesn't scale very well for people located all over the globe (i.e. anywhere outside EU).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Starting from now, we are using Cloudflare CDN to serve attachments to users which means you'll see much, much faster download speeds regardless of where you are located.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Note: Your attachments are always encrypted on your device before being sent to the server so no one, us or Cloudflare, can read or access those contents. The only thing they see is an encrypted blob. Not even the filenames are visible so your attachments are 100% secure - just a lot faster now.</p>
</blockquote>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed update indicator on desktop app</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Basically, the desktop app didn't automatically check for updates nor did it show whether an update was available or not. This created the false illusion that the app was on the latest version.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Of course, the update did happen in the background so there was nothing to worry about but still, seeing no indication creates a lot of confusion.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This has been fixed now.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Added support for clearing attachments cache</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now clear the local attachments cache in case you are facing a persistent issue with an attachment (e.g. attachment not downloading because the app thinks it's already downloaded).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This should <strong>only</strong> be used for troubleshooting purposes. If you are facing an issue repeatedly, please report it to us via email at <a href="mailto:support@streetwriters.co" target="_blank" class="c_accent">support@streetwriters.co</a></p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.11...v3.0.12" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.11]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.11</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.11</guid>
            <pubDate>Thu, 18 Jul 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 10 bugs via 24 commits. Editor session data is now stored encrypted, fixed various editor related issues especially with pasting content, and other performance optimizations.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.11 fixes 10 bugs via 24 commits. Editor session data is now stored encrypted by default, fixed various editor related issues especially with pasting content, and other performance optimizations.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.10/" target="_blank" class="c_accent"><code style="color:#dd028d">v3.0.10</code></a> fixes 5 bugs via 6 commits. Fixed weird UI glitches when scrolling in different lists, Pro popup is now shown only if there are images when pasting, and markdown shortcuts are working again.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.9/" target="_blank" class="c_accent"><code style="color:#a911c4">v3.0.9</code></a> fixes 15+ bugs via 28 commits. Fixed web app for mobile browsers, images now automatically get removed on paste for non-Pro users, and real-time editor sync now works again.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.7/" target="_blank" class="c_accent"><code style="color:#d615bf">v3.0.8</code></a> fixes a critical bug where the app would crash on start up if app lock was enabled.</li>
</ul>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Editor session data is now stored encrypted by default</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Ever since we released tabs in v3, we have been storing the editor session data (i.e. data related to opened tabs) in LocalStorage. This worked fine but it leaked the note titles for all the tabs you have opened in Notesnook.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Starting from this version, all your tabs data will be stored alongside your notes &amp; other data encrypted by default.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Note: After upgrading to v3.0.11, all your opened tabs will automatically be closed. This is a one time operation and everything will work as usual afterwards.</strong></p>
</blockquote>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed various pasting issues in editor</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Pasting content and making sure it appears "just" right is a pain. In this release, pasting content containing lists will work much more smoothly. We have also fixed a long standing issue where pasting multiple lists only pasted the first list ignoring everything else.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed updating a link creates a new link beside it</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This issue only appeared in the web/desktop app when updating the link via the hover popup. The editor was trying to update the link based on the current cursor position which could be anywhere, causing it to think that it should add a link instead of updating the existing one.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed editor not scrolling</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Since v3, we have been saving and restoring scroll position for all notes. To keep things working for really long notes, we set a <code style="color:#258400">minHeight</code> to the scroll container in case the note wasn't done loading yet. This made sure that the scroll position would be correct once the content <em>was</em> loaded, however, it also broke the scroll because now the scroll container was the same size as the content inside it.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Performance improvements</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Nothing major related to performance except we have gotten rid of <code style="color:#e817e1">immer</code> and replaced it with <code style="color:#dd16d3">mutative</code> for immutable state management inside the app. <code style="color:#dd16d3">mutative</code> is almost 10x faster so you'll notice slight improvements in app responsiveness.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In this release, we have also refactored how dialogs &amp; modals are loaded and shown. Previously, we had all the modal showing logic in one module which was really bad for treeshaking and page load times. We have now isolated each modal to its own module so app load times will be much, much faster.</p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.10...v3.0.11" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.10]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.10</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.10</guid>
            <pubDate>Wed, 10 Jul 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 5 bugs via 6 commits. Fixed weird UI glitches when scrolling in different lists, Pro popup is now shown only if there are images when pasting, and markdown shortcuts are working again.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.10 fixes 5 bugs via 6 commits. Fixed weird UI glitches when scrolling in different lists, Pro popup is now shown only if there are images when pasting, and markdown shortcuts are working again.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.9/" target="_blank" class="c_accent"><code style="color:#a911c4">v3.0.9</code></a> fixes 15+ bugs via 28 commits. Fixed web app for mobile browsers, images now automatically get removed on paste for non-Pro users, and real-time editor sync now works again.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.7/" target="_blank" class="c_accent"><code style="color:#d615bf">v3.0.8</code></a> fixes a critical bug where the app would crash on start up if app lock was enabled.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.7/" target="_blank" class="c_accent"><code style="color:#d114ae">v3.0.7</code></a> fixes 15+ bugs via 42 commits. Themable title bar, logs are now stored in a SQLite database, 10x faster web app loading, and undo/redo buttons are back.</li>
</ul>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed crashes when running the web app in older browsers</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook heavily depends on OPFS for SQLite to function properly. For older browsers we also provide an IndexedDB based fallback. Since OPFS doesn't currently support access to the same database from multiple tabs, we have also implemented a shared service which basically shares the same connection across tabs. This works for the OPFS backend but fails spectacularly when the IndexedDB backend is used.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It turns out browsers natively support multi tab access in IndexedDB so there's no need to use our shared service when using the IndexedDB backend. This fix is similar to <a href="https://blog.notesnook.com/notesnook-v3.0.9/" target="_blank" class="c_accent">the one we made for Notesnook to run on mobile browsers in <code style="color:#a911c4">v3.0.9</code></a>.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed markdown shortcuts not working in editor</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was caused by a fix we added in order to support changing editor settings without restarting the editor. However, TipTap currently doesn't support reusing the same editor while refreshing the plugins/extensions so it caused various issues in different extensions and also broke the markdown shortcuts for almost everything.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In this release, we have applied an alternative way to support changing editor settings in real time.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed Pro popup shown even if text is pasted</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was mostly due to laziness: we were checking (and showing the popup) for images access even if there were no <code style="color:#c40159">img</code> tags in the pasted content.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed scroll glitches in tags &amp; other lists</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">These scroll glitches have been there since v3.0.0, and were being caused by wrong default item size being given to the underlying list renderer. Notesnook heavily uses virtualization to render insane amount of items, but that means we have no idea exactly how large the list can be so we use the next best thing: a rough estimate.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The glitch was occuring because the list's estimate of the total height was 2x more than the actual height.</p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.9...v3.0.10" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.9]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.9</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.9</guid>
            <pubDate>Mon, 08 Jul 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 15+ bugs via 28 commits. Fixed web app for mobile browsers, images now automatically get removed on paste for non-Pro users, and real-time editor sync now works again.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.9 fixes 15+ bugs via 28 commits. Fixed web app for mobile browsers, images now automatically get removed on paste for non-Pro users, and real-time editor sync now works again.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.7/" target="_blank" class="c_accent"><code style="color:#d615bf">v3.0.8</code></a> fixes a critical bug where the app would crash on start up if app lock was enabled.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.7/" target="_blank" class="c_accent"><code style="color:#d114ae">v3.0.7</code></a> fixes 15+ bugs via 42 commits. Themable title bar, logs are now stored in a SQLite database, 10x faster web app loading, and undo/redo buttons are back.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.5/" target="_blank" class="c_accent"><code style="color:#a20ecc">v3.0.6</code></a> fixes an annoying issue where notes with no actual conflict appeared as conflicted.</li>
</ul>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Web app for mobile browsers</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Ever since the v3 release, the web app has been completely broken on mobile browsers. Users couldn't even load the login/sign up page all because mobile browsers currently do not support <code style="color:#b72a07">SharedWorker</code> which is required for cross-tab sharing of an SQLite connection.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have fixed this by disable multi-tab support on browsers that don't support <code style="color:#b72a07">SharedWorker</code> (e.g. Chrome &amp; Safari).</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed real time sync not updating content inside editor</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This annoying bug was caused by a check we added to prevent newly made edits from being overwritten by a delayed real time sync. That worked by comparing the time of the last change with modified time on the synced item. While that functionality worked, it also started ignoring valid syncs for notes that you just opened or if you restarted the app.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The issue was caused by this line of code:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> lastChangedTime <span class="token operator">=</span> <span class="token generic-function"><span class="token function">useRef</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token builtin">number</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Later on when a real time sync would occur, we'd check it like so:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">if</span> <span class="token punctuation">(</span>isContent <span class="token operator">&amp;&amp;</span> lastChangedTime<span class="token punctuation">.</span>current <span class="token operator">&lt;</span> item<span class="token punctuation">.</span>dateModified<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token comment">// ...</span>
<span class="ln-num" data-num="3"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For newly opened notes (or if you restarted the app), the value of <code style="color:#048c0b">lastChangedTime</code> would always be greater than <code style="color:#d20be0">item.dateModified</code> causing the app to continuously ignore the sync until you made an edit.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The fix was simple:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> lastChangedTime <span class="token operator">=</span> <span class="token generic-function"><span class="token function">useRef</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token builtin">number</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And viola! Everything works again.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Images are now removed automatically on paste for non-Pro users</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was causing annoying issues where non-Pro users were able to paste images but it would break their sync. Starting from this version, all images will automatically be removed on paste (without affecting anything else) for non-Pro users.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you are on the Basic plan and getting "unable to resolve attachment url" errors over and over again, follow these steps to resolve the issue:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Go to Settings &gt; Profile &gt; Attachments manager</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Switch to the Uploads tab</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Select all the pending uploads</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Delete them</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This will fix your sync.</p>
</blockquote>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read the full commit history <a href="https://github.com/streetwriters/notesnook/compare/v3.0.8...v3.0.9" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.7]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.7</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.7</guid>
            <pubDate>Mon, 03 Jun 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 15+ bugs via 42 commits. Themable title bar, logs are now stored in a SQLite database, 10x faster web app loading, and bring back undo/redo buttons.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.7 fixes 15+ bugs via 42 commits. Themable title bar, logs are now stored in a SQLite database, 10x faster web app loading, and undo/redo buttons are back.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.5/" target="_blank" class="c_accent"><code style="color:#a20ecc">v3.0.6</code></a> fixes an annoying issue where notes with no actual conflict appeared as conflicted.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.5/" target="_blank" class="c_accent"><code style="color:#e20b89">v3.0.5</code></a> fixes 10+ bugs via 37 commits. Improved conflict handling during sync, fixed web app on Safari, added support for duplicating locked notes, brought back <code style="color:#0ba544">Ctrl + F</code> to search all notes, and fixed search/replace in editor.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.3/" target="_blank" class="c_accent"><code style="color:#84063b">v3.0.4</code></a> fixes a critical bug with 2FA emails/SMS not getting sent during login. Everything else is the same as v3.0.3.</li>
</ul>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Themable title bar</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have added a new theme scope to independently theme the title bar in the desktop app. Here's how it looks:</p>
<div class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/forevernote-dark-titlebar.CjB_dUVM.webp 600w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/forevernote-dark-titlebar.C60f2BhY.png 600w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/forevernote-dark-titlebar.C60f2BhY.png" srcset="https://blog.notesnook.com/assets/static/forevernote-dark-titlebar.C60f2BhY.png 600w" sizes="100vw" alt="Themed title bar in Forevernote Dark theme." style="max-width:600px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Themed title bar in Forevernote Dark theme.</figcaption></figure><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/nord-theme-titlebar.Bfzp8qzN.webp 594w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/nord-theme-titlebar.BZt5N_6k.png 594w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/nord-theme-titlebar.BZt5N_6k.png" srcset="https://blog.notesnook.com/assets/static/nord-theme-titlebar.BZt5N_6k.png 594w" sizes="100vw" alt="Themed title bar in Nord theme." style="max-width:594px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Themed title bar in Nord theme.</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">&nbsp;</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can also theme the individual tab items using the same theme scope.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Logs in SQLite Database</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In v3, we migrated everything except app logs to SQLite. Unfortunately, in the last few versions users have reported sudden app hangs and other lag issues. This occured whenever the app tried to clear the old app logs which could be in the millions. The mobile app was most affected by this and issues like notes not saving also occured.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In this version, we are now moving the logs to SQLite fixing all these issues.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">10x faster web app loading</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I recently read <a href="https://brr.fyi/posts/engineering-for-slow-internet" target="_blank" class="c_accent">brr's Engineering for Slow Internet</a> and <a href="https://danluu.com/slow-device/" target="_blank" class="c_accent">Dan Luu's How web bloat impacts users with slow devices</a>, and decided to benchmark the Notesnook Web app on a throttled slow 3G internet connection to see how it performs. No surprise. There was a lot of room for improvement.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Our web app was taking around 1.5 minutes to fully become operational. That is not the nicest experience. The worst part is how it just sat there for 100 seconds stuck on the loading screen. Whenever something like this happens, the first thing to check is how the app is being bundled i.e., if there are too many or too few chunks, the loading times will increase dramatically.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In our case, the bundling was completely broken. We were loading a ~450 KB chunk upfront and then very slowly loading the other chunks almost sequentially. Notesnook is a PWA so, naturally, it uses a service worker to precache all assets for later loading. Unfortunately, it was precaching too many unnecessary modules causing a significant slow down for first time users.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The optimization was simple: slim down the bootstrap module &amp; load the service worker as early as possible to load all the precached modules in parallel. An additional step was to break down a few big chunks into smaller chunks to ease loading (but that only helped a little).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The result? The web app now <strong>loads in 10-15 seconds</strong> on a slow 3G internet connection.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Undo/redo buttons are back</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In v3, we removed the undo/redo buttons on desktop/web app (because there are keyboard shortcuts right?). On the mobile app, we moved them inside the drop down menu. After several user requests, we are reverting this change.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">SQlite on another thread</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In the desktop app, we were running SQLite queries on the same thread as the app. This worked alright for fast queries but caused significant lag for slow or too frequently used queries e.g. when editing the note. We have now moved SQLite to its own thread so it should no longer block the main thread when executing queries creating a much smoother experience.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Use native title bar on desktop app</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A few users requested that they do not want the custom title bar with integrated tabs so in this release, we are adding an option in Settings &gt; Desktop integration to disable the custom title bar and use the native one instead.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed sync not running on window/page focus</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have fixed an issue causing the sync to not run when the app was brought into foreground either after minimizing or moving the focus away.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed extra padding around images</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This was reported early on in v3 but we couldn't get to it due to other more critical issues. This release fixes the bug and all your images should automatically appear correctly.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.5]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.5</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.5</guid>
            <pubDate>Thu, 16 May 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 10+ bugs via 37 commits. Improved conflict handling during sync, fixed web app on Safari, added support for duplicating locked notes, brought back `Ctrl + F` to search all notes, and fixed search/replace in editor.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.5 fixes 10+ bugs via 37 commits. Improved conflict handling during sync, fixed web app on Safari, added support for duplicating locked notes, brought back <code style="color:#0ba544">Ctrl + F</code> to search all notes, and fixed search/replace in editor.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.3/" target="_blank" class="c_accent"><code style="color:#84063b">v3.0.4</code></a> fixes a critical bug with 2FA emails/SMS not getting sent during login. Everything else is the same as v3.0.3.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.3/" target="_blank" class="c_accent"><code style="color:#8f09c4">v3.0.3</code></a> fixes 15+ bugs via 40 commits. Replaced force sync with separate force push &amp; force pull buttons, added various fixes to real-time sync, improved editor reliability on mobile &amp; fixed export of locked notes.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.2/" target="_blank" class="c_accent"><code style="color:#ed12d7">v3.0.2</code></a> fixes 10+ bugs via 24 commits. Attachment manager for logged out users, much improved attachment handling fixing a couple of critical issues, pre-v3 backups no longer throw errors, and many other fixes.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.1/" target="_blank" class="c_accent"><code style="color:#b309e2">v3.0.1</code></a> fixes 20+ bugs via 65+ commits. This release adds SQL based search, improves editor performance &amp; add support for multiline search replace in editor.</li>
</ul>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Improved conflict handling during sync</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Ever since releasing v3, sync related issues have <em>almost</em> been reduced to 0 (emphasis on almost). While we fixed "my notes aren't syncing", we now have a new issue: "my note got reverted to an older version". I don't know which is worse but in this release, we tracked down and fixed this issue as well.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Here's a scenario:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">You are editing a note while offline on your laptop.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">A few hours later, you open and edit the same note on your mobile (which is online and syncs the changes immediately).</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Next morning, you open your laptop, sync runs, but you don't see the changes you made on the mobile. You open your mobile app and see that the note has reverted all your changes.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">At this point, you either delete the app or, if you are handy with a phone, you restore your changes from the note history. The question remains though, why did this happen? Shouldn't newer changes almost always take priority over older changes in sync?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Turns out we were bumping the <code style="color:#2b8e0c">dateModified</code> property on each item after pushing it to the server. If you remember from my previous posts on how the sync works, we always set the <code style="color:#e5067d">synced</code> property to <code style="color:#e00280">true</code> after syncing each item. What happened was, we were also bumping the <code style="color:#2b8e0c">dateModified</code> property along with changing the <code style="color:#e5067d">synced</code> property.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">How does the app know which item is newer? By using <code style="color:#2b8e0c">dateModified</code>. So imagine that you edited something at 5 PM but synced it at 10 PM. What would be the value of <code style="color:#2b8e0c">dateModified</code>? 10 PM. And that is the bug.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The fix was simple. We make sure that nothing touches the <code style="color:#2b8e0c">dateModified</code> property. However, that still doesn't fix the problem.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Even if the above issue never occurs, you'll notice how your unsynced changes from the laptop would get replaced by the newer changes made on your mobile device. Is that really the intended behavior? What if you noted down something really important?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To handle this unique situation, Notesnook will now trigger a conflict if the version of the item you have on your device isn't synced. It won't care if it is older or newer. If it is unsynced that means there is important information and you should have the choice of how to preserve the information.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed note duplication (including support for duplicating locked notes)</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">v3 broke note duplication almost completely. While we were duplicating the title &amp; content of the note, everything else, like the notebook, tag or color, was completely ignored. This is now fixed.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Another unintentional limitation was that we didn't allow duplicating locked notes (for some reason). That made no sense, though, so it has been removed.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Bring back <code style="color:#b55309">Ctrl + f</code> to search all notes</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I didn't even know this was broken until I tried to search in a readonly editor and noticed the notes list flickering like a light bulb. Turns out it was trying to navigate to <code style="color:#ce08c7">/notes/search</code> which no longer exists. We now handle all searching within the same route. This is now fixed and pressing <code style="color:#b55309">Ctrl + f</code> anywhere outside the editor will open notes search.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed web app not working reliably on Safari</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Safari has some really frustrating problems when using Workers. It doesn't auto close a worker when you navigate or refresh the page, it also doesn't free any WebLocks automatically. This results in the app getting badly stuck on load, and the only way to work around it is to close the tab and open the app in another tab. Not really convenient.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This has now been fixed. Notesnook now works on Safari just like on other browsers.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed password/pin based app lock on Android</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This issue took us multiple days to figure out. And the fix?</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/app-lock-fix-diff.DvmCXQci.webp 633w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/app-lock-fix-diff.DGNmGTKG.png 633w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/app-lock-fix-diff.DGNmGTKG.png" srcset="https://blog.notesnook.com/assets/static/app-lock-fix-diff.DGNmGTKG.png 633w" sizes="100vw" alt="Multiline search &amp; replace." style="max-width:633px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Multiline search &amp; replace.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We were using the wrong size salt: 12 bytes instead of 16 bytes. Turns out that triggers undefined behavior instead of throwing an error. Fixing this was as easy as replacing the old salt with new one.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">What about people who already have app lock enabled? Well, we have added an option to remove the app lock using your account password. If that doesn't work, you can always just reinstall the app (not as convenient but no other way, unfortunately).</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed pricing plans stuck on loading on Android/iOS</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This issue wasn't highlighted a lot even though it has been occurring for quite some time. Notesnook uses regional pricing which requires getting the appropriate SKU/ProductID depending on where you are located. This is handled by a Cloudflare Worker.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When we were migrating the Notesnook Core to TypeScript, I had changed the signature for the class responsible for communicating with the Cloudflare Worker. Unfortunately, I forgot to make the appropriate changes in the mobile app resulting in the app always getting stuck when getting the pricing information.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This has now been fixed, and you'll be able to purchase a new subscription from Android/iOS just like before.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Taking screenshots with app lock turned on</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Normally when you turn on the app lock, the app automatically disables screenshot taking. This is the default behaviour. However, sometimes you might want to take screenshots without disabling the app lock.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In this release, you can disable Privacy Mode from Settings (on by default when you enable app lock) to take your screenshots.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.3]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.3</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.3</guid>
            <pubDate>Fri, 10 May 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 15+ bugs via 40 commits. Replaced force sync with separate force push & force pull buttons, added various fixes to real-time sync, improved editor reliability on mobile & fixed export of locked notes.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.3 fixes 15+ bugs via 40 commits. Replaced force sync with separate force push &amp; force pull buttons, added various fixes to real-time sync, improved editor reliability on mobile &amp; fixed export of locked notes.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.2/" target="_blank" class="c_accent"><code style="color:#ed12d7">v3.0.2</code></a> fixes 10+ bugs via 24 commits. Attachment manager for logged out users, much improved attachment handling fixing a couple of critical issues, pre-v3 backups no longer throw errors, and many other fixes.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.1/" target="_blank" class="c_accent"><code style="color:#b309e2">v3.0.1</code></a> fixes 20+ bugs via 65+ commits. This release adds SQL based search, improves editor performance &amp; add support for multiline search replace in editor.</li>
</ul>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Replaced force sync with force push/pull</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The reason is simple: force sync is rarely useful on its own and, more often than not, it causes data loss. However, we can't just remove it because there <em>is</em> sometimes need for a "force" sync. I also think a lot of users have been misusing "force sync" as an alternative to auto sync because of sync issues that were common in v2. That is no longer the case which is why we are replacing "Force sync" with separate buttons for "Force pull" and "Force push".</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/force-push-pull-sync.URp6WViK.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/force-push-pull-sync.CDHBwRt4.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/force-push-pull-sync.CDHBwRt4.png" srcset="https://blog.notesnook.com/assets/static/force-push-pull-sync.CDHBwRt4.png 640w" sizes="100vw" alt="Multiline search &amp; replace." style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Multiline search &amp; replace.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In v2 (and v3), "Force sync" used to work as follows:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Get everything that's on the server</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Send everything that's on the device</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In most cases, one of the above steps is unnecessary because either you want to get everything from the server or send everything from your device to the server. In cases where you need both, it's always better to control the sequence of those 2 steps.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>NOTE: As mentioned in the screenshot above, this should never be used except for troubleshooting purposes. For 99.99% of cases, auto sync should work and you should never face a scenario where "force pushing" or "force pulling" is needed.</strong></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">More reliable mobile editor</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have recently been getting a couple of bug reports from (mostly) mobile users where their edits are not saved. This is a critical issue and in this release we have added various fail safes to prevent critical data loss.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In the mobile app, the editor and the app are basically 2 different processes and they communicate internally through an IPC. The editor runs inside a WebView and any edit you make gets sent to the app for storage and syncing. Sometimes, however, this IPC can fail or stop responding resulting in data loss. To prevent such issues, we have now added fail safes which will notify you if the communication with the app is broken, or if saving a note takes too long. You can then take extra measures such as copying your changes.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is not a common occurence, by any means, and the app will try to automatically recover your edits (if possible).</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed <code style="color:#044d6b">File already exists</code> error on duplicate downloads</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Downloading the same file over and over again (i.e. when you have multiple copies of an image in a single note) will no longer fail with the <code style="color:#044d6b">File already exists</code> error.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed <code style="color:#9206c9">database disk image malformed</code> error</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>This issue was partially fixed in <a href="https://blog.notesnook.com/notesnook-v3.0.1/" target="_blank" class="c_accent"><code style="color:#b309e2">v3.0.1</code></a>.</em></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">By default, SQLite FTS5 indexes everything in the table and there's no way to exclude anything. To avoid the unnecessary indexing of locked notes (which are basically gibberish), and permanently deleted notes (which don't have anything worth indexing), we use SQLite triggers with some conditions. The problem occurs when SQLite tries to deindex something that wasn't indexed in the first place.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This can happen, for example, when syncing (or restoring a backup) that contains locked notes and then permanently deleting those notes from trash. Unfortunately, debugging this is next to impossible due to very little information exposed by SQLite.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This version also rebuilds the whole FTS5 index (so if you have a lot of notes it might take some time to start the app after upgrading). This is necessary because due to incorrect logic, previous versions were indexing blacklisted items that shouldn't be indexed. To properly fix the issue, it was necessary to reindex everything.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed edge case during sync causing some items to be missed out</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">While the new sync engine handles everything without jumping through a whole lot of hoops, it isn't immune to some edge cases. The problem is simple: what would happen if an item gets updated right when it is being pushed to the server?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you remember from the <a href="https://blog.notesnook.com/introducing-notsnook-v3/" target="_blank" class="c_accent">Introducing Notesnook v3 blog post</a>, the sync engine basically operates on the value of <code style="color:#e5067d">synced</code>. Now imagine the following scenario:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Item A is edited (it's <code style="color:#e5067d">synced</code> property is set to <code style="color:#e50ba0">false</code>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Sync engine pushes Item A to the server</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Sync engine sets <code style="color:#e5067d">synced</code> property of Item A to <code style="color:#e00280">true</code></li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">All well and good so far, but what if Item A gets updated between step 2 &amp; 3? Oops. The change will never be pushed to the server because it's <code style="color:#e5067d">synced</code> property will be set to <code style="color:#e00280">true</code> in step 3.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To circumvent this edge case, we now only toggle the <code style="color:#e5067d">synced</code> property if the item wasn't changed while it was being pushed to the server. Thanks to SQLite, this only requires adding the following condition:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-sql"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">WHERE</span> dateModified <span class="token operator">&lt;=</span> timestampBeforePushStarted</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This fixes the issue but the item still isn't synced until the next sync is run (which can never happen if nothing else changes). That is why, right after the sync completes we check if there are any items that were modified during the current sync cycle, and if so, those items immediately get sent to the server. In this way, your edits are never missed.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other changes</h3>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix tests <a href="https://github.com/streetwriters/notesnook/commit/919524489" target="_blank" class="c_accent">919524489</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix image filename in picker <a href="https://github.com/streetwriters/notesnook/commit/152794ccd" target="_blank" class="c_accent">152794ccd</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix editor content update on realtime sync <a href="https://github.com/streetwriters/notesnook/commit/2e8b234b6" target="_blank" class="c_accent">2e8b234b6</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Add <code style="color:#91220e">Audios</code> tab in attachments dialog <a href="https://github.com/streetwriters/notesnook/commit/055c64769" target="_blank" class="c_accent">055c64769</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix real time sync test <a href="https://github.com/streetwriters/notesnook/commit/3c8b8a7bc" target="_blank" class="c_accent">3c8b8a7bc</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Saving timeout notice shown every time <a href="https://github.com/streetwriters/notesnook/commit/fde45fb6d" target="_blank" class="c_accent">fde45fb6d</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix tab not updating when note is locked/unlocked <a href="https://github.com/streetwriters/notesnook/commit/4a616a3e6" target="_blank" class="c_accent">4a616a3e6</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix dateEdited not updating on saving locked content <a href="https://github.com/streetwriters/notesnook/commit/bd0a338dd" target="_blank" class="c_accent">bd0a338dd</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix async iterator on sql collection only returning <code style="color:#007c4f">dateCreated</code> &amp; <code style="color:#e21482">id</code> <a href="https://github.com/streetwriters/notesnook/commit/5ab721e3e" target="_blank" class="c_accent">5ab721e3e</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix errors <a href="https://github.com/streetwriters/notesnook/commit/cb70bb942" target="_blank" class="c_accent">cb70bb942</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Show error if saving takes more than 30 seconds <a href="https://github.com/streetwriters/notesnook/commit/82cadd473" target="_blank" class="c_accent">82cadd473</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix editor loading <a href="https://github.com/streetwriters/notesnook/commit/0e8ea3dde" target="_blank" class="c_accent">0e8ea3dde</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Handle editor not responding <a href="https://github.com/streetwriters/notesnook/commit/511acc5fc" target="_blank" class="c_accent">511acc5fc</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Add editor lock safety <a href="https://github.com/streetwriters/notesnook/commit/001cf8ee9" target="_blank" class="c_accent">001cf8ee9</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix color not updating on note (#5573) <a href="https://github.com/streetwriters/notesnook/commit/a965cd694" target="_blank" class="c_accent">a965cd694</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix wording of push/pull sync <a href="https://github.com/streetwriters/notesnook/commit/d3c1c1959" target="_blank" class="c_accent">d3c1c1959</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Replace force sync with force push/pull <a href="https://github.com/streetwriters/notesnook/commit/9a866a552" target="_blank" class="c_accent">9a866a552</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Push/pull force sync <a href="https://github.com/streetwriters/notesnook/commit/837c29682" target="_blank" class="c_accent">837c29682</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix locked notes not getting exported <a href="https://github.com/streetwriters/notesnook/commit/9aed6faed" target="_blank" class="c_accent">9aed6faed</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix crash due to <code style="color:#ac01c6">undefined is not an object (evaluating 'a.nodeSize')</code> (#5553) <a href="https://github.com/streetwriters/notesnook/commit/734f491e8" target="_blank" class="c_accent">734f491e8</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix editor crash due to position (#5519) <a href="https://github.com/streetwriters/notesnook/commit/0ea13024e" target="_blank" class="c_accent">0ea13024e</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Close shared service on window close <a href="https://github.com/streetwriters/notesnook/commit/cb2703924" target="_blank" class="c_accent">cb2703924</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Do not use auth access tokens as normal access tokens <a href="https://github.com/streetwriters/notesnook/commit/714ac2dea" target="_blank" class="c_accent">714ac2dea</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix crash on toggling custom dns (#5538) <a href="https://github.com/streetwriters/notesnook/commit/d605bc29b" target="_blank" class="c_accent">d605bc29b</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix <code style="color:#9206c9">database disk image malformed</code> error (#5525) <a href="https://github.com/streetwriters/notesnook/commit/080101bc4" target="_blank" class="c_accent">080101bc4</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Rerun sync if there are unsynced changes after sync complete <a href="https://github.com/streetwriters/notesnook/commit/4819e17da" target="_blank" class="c_accent">4819e17da</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix crash when taking camera picture <a href="https://github.com/streetwriters/notesnook/commit/68a6d6c31" target="_blank" class="c_accent">68a6d6c31</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix camera permission <a href="https://github.com/streetwriters/notesnook/commit/c51ef4d5f" target="_blank" class="c_accent">c51ef4d5f</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Remove margin in editor <a href="https://github.com/streetwriters/notesnook/commit/d9c5c1f89" target="_blank" class="c_accent">d9c5c1f89</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix editor scrolling on update <a href="https://github.com/streetwriters/notesnook/commit/5a75ca73a" target="_blank" class="c_accent">5a75ca73a</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Use yarn instead of npx for build scripts <a href="https://github.com/streetwriters/notesnook/commit/626d39337" target="_blank" class="c_accent">626d39337</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix legacy backup restoration <a href="https://github.com/streetwriters/notesnook/commit/68d06ff3f" target="_blank" class="c_accent">68d06ff3f</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix <code style="color:#044d6b">File already exists</code> error on duplicate downloads <a href="https://github.com/streetwriters/notesnook/commit/79f20d100" target="_blank" class="c_accent">79f20d100</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Handle case when updating item while a push is ongoing <a href="https://github.com/streetwriters/notesnook/commit/ac96354b4" target="_blank" class="c_accent">ac96354b4</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Set synced to false on removing content by note id <a href="https://github.com/streetwriters/notesnook/commit/63f44d6fb" target="_blank" class="c_accent">63f44d6fb</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Set synced to false on unlinking relations <a href="https://github.com/streetwriters/notesnook/commit/f9bfa88c8" target="_blank" class="c_accent">f9bfa88c8</a></li>
</ul>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.2]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.2</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.2</guid>
            <pubDate>Sun, 05 May 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 10+ bugs via 24 commits. Attachment manager for logged out users, much improved attachment handling fixing a couple of critical issues, pre-v3 backups no longer throw errors, and many other fixes.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.2 fixes 10+ bugs via 24 commits. Attachment manager for logged out users, much improved attachment handling fixing a couple of critical issues, pre-v3 backups no longer throw errors, and many other fixes.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Previous releases:</strong></p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-v3.0.1/" target="_blank" class="c_accent"><code style="color:#b309e2">v3.0.1</code></a> fixes 20+ bugs via 65+ commits. This release adds SQL based search, improves editor performance &amp; add support for multiline search replace in editor.</li>
</ul>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Attachment manager is now accessible without logging in</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is especially useful when you are restoring backups in order to see find or restore a specific attachment without first logging into an account.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed unnecessary attachment overwriting during sync conflict</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There was a logical error in our sync process causing all attachments to get overwritten with their locally cached files removed. This logic was added to handle the edge case where 2 devices upload the same attachment. However, it also triggered when that wasn't the case.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This bug is also the reason why your locally cached images &amp; PDFs sometimes get removed and have to be redownloaded.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed account recovery redirecting to the sign up page</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This issue was surprising especially because we didn't touch anything related to auth or account recovery in v3. Turns out we were double initializing the SQLite database during the authentication step which throws a <code style="color:#160875">Driver not initialized</code> error. Any error during the authentication step just redirects the user to the sign up page (this is probably not a good idea and we should show the actual error instead).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This has now been fixed and account recovery should work as before.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed unnecessary attachment uploading on a failed recheck</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There was a really weird issue causing an attachment to unnecessarily be marked for uploading any time an error occured during download or recheck. The constantly happened even if the error was due to a network issue.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed <code style="color:#88990d">Invalid backup</code> on restoring some backups</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Last year when we changed our backup format to a .zip file, we used to store an empty <code style="color:#9b0dbf">.nnbackup</code> file for "verification" purposes. Unfortunately, when taking a backup before migrating to v3, this empty file wasn't written to the backup file causing the <code style="color:#88990d">Invalid backup</code> error on restore.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In this release, we have removed this check as it wasn't very foolproof in the first place. This means that trying to restore a random .zip as a backup file will probably fail in some other unexpected way.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">(Properly) fixed <code style="color:#c4aa03">Please login to create an encrypted backup.</code> error during migration</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In v3.0.1 we fixed this error but the root cause was not addressed. When you migrate to v3 from v2 app, it takes an automatic backup. This backup is taken using the "legacy" backup logic that doesn't use SQLite etc.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The problem occured when due to some unexpected reason, the user's account information got removed from the legacy database. This made the app think that the user wasn't logged in and, hence, threw the error.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Since the most important thing is to backup the data before migration (not the encryption), the app now automatically turns encryption off if it cannot find the user when taking the "legacy" backup. <strong>NOTE: This is only for legacy backups. Taking a v3 backups with encryption turned on but without user information will still throw an error.</strong></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other fixes</h3>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix image picker on mobile by @ammarahm-ed <a href="https://github.com/streetwriters/notesnook/pull/5304" target="_blank" class="c_accent">#5304</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix n.startsWith is not a function by @ammarahm-ed <a href="https://github.com/streetwriters/notesnook/pull/5266" target="_blank" class="c_accent">#5266</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix attachment grouping and adds logging by @ammarahm-ed <a href="https://github.com/streetwriters/notesnook/pull/5267" target="_blank" class="c_accent">#5267</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix note not opening from pinned notifications by @ammarahm-ed <a href="https://github.com/streetwriters/notesnook/pull/5300" target="_blank" class="c_accent">#5300</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix crash due to unsanitized sort options by @alihamuh <a href="https://github.com/streetwriters/notesnook/pull/5324" target="_blank" class="c_accent">#5324</a></li>
</ul>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v3.0.1]]></title>
            <link>https://blog.notesnook.com/notesnook-v3.0.1</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-v3.0.1</guid>
            <pubDate>Wed, 01 May 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[Fixed 20+ bugs via 65+ commits. SQL based search, improved editor performance & multiline search replace in editor.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook v3.0.1 fixes 20+ bugs via 65+ commits. This release adds SQL based search, improves editor performance &amp; add support for multiline search replace in editor.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">SQL Based Search</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook now supports the full FTS5 search syntax. Here's a quick summary about what it'll enable:</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Wrap a phrase in quotes (e.g. "&lt;phrase&gt;") to run exact search</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Simple queries (such as "best blog ideas") will look for the words <code style="color:#e002a5">best</code>, <code style="color:#9c06d3">blog</code> &amp; <code style="color:#c10fbb">ideas</code> and show all notes that contain these words regardless of their sequence.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">You can use boolean operators <code style="color:#0e1091">OR</code>, <code style="color:#1f0682">AND</code> &amp; <code style="color:#190487">NOT</code> to further narrow down results. For example: <code style="color:#ce09ed">notesnook OR evernote</code> will show all notes that either have the word <code style="color:#e2047b">notesnook</code> or <code style="color:#e00878">evernote</code> in them.</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The FTS5 query syntax supports a lot of functionality which you can <a href="https://sqlite.org/fts5.html#full_text_query_syntax" target="_blank" class="c_accent">read about here</a>.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Multiline search &amp; replace</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now search and replacing across lines using Regex.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/multiline-search-replace.CELFLOgl.webp 640w, https://blog.notesnook.com/assets/static/multiline-search-replace.Sb8MW9GN.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/multiline-search-replace.B6pYU8EZ.png 640w, https://blog.notesnook.com/assets/static/multiline-search-replace.CkahpUsS.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/multiline-search-replace.B6pYU8EZ.png" srcset="https://blog.notesnook.com/assets/static/multiline-search-replace.B6pYU8EZ.png 640w, https://blog.notesnook.com/assets/static/multiline-search-replace.CkahpUsS.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Multiline search &amp; replace." class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Multiline search &amp; replace.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I have also improved the overall UX of search replace so it now works much more smoothly:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Turning on Regex search automatically reruns the search without you having to press <code style="color:#b5140e">Enter</code> again. Same for toggling other things like <code style="color:#017058">Match whole word</code>, <code style="color:#c9c600">Match case</code> etc.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#237a03">Shift + Enter</code> moves to the previous search result.</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed <code style="color:#ce16e2">database disk image is malformed</code> error on sync</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This error occured whenever the sync engine tried to update a deleted note or content item. Deleted items have the following structure:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-json"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token property">"id"</span><span class="token operator">:</span> <span class="token string">"some_long_hex_id"</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span>  <span class="token property">"deleted"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="4"></span>  <span class="token property">"dateModified"</span><span class="token operator">:</span> <span class="token number">100000000000</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="5"></span>  <span class="token property">"synced"</span><span class="token operator">:</span> <span class="token boolean">true</span>
<span class="ln-num" data-num="6"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Whenever a content or a note item is updated, NN runs an SQLite trigger that updates the FTS5 search index with the new values. This worked in most cases but when it tried to update the index based on the deleted item, it didn't find the required fields (<code style="color:#55930d">noteId</code> &amp; <code style="color:#a513d6">data</code>) resulting in the above error. The bug didn't affect any data or cause any permanent corruption.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The fix was to only run the trigger if and only if the item isn't deleted and has all the required fields.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can review the changes in this PR: <a href="https://github.com/streetwriters/notesnook/pull/4957" target="_blank" class="c_accent">#4957</a>.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Fixed editor crash with error <code style="color:#3a9100">Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.</code></h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Upgrading to React 18 brought with it a bunch of stupid crashes. This particular crash occurs in such a way that there's no way to catch it. Let's start from the beginning:</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In order to render custom react node views inside the editor, we have to use React Portals. However, ProseMirror doesn't actually support the React way of doing things. ProseMirror is 100% synchronous while React is asynchronous and operates on a virtual dom. The problem occurs when ProseMirror removes a DOM node which is still present in React's Virtual DOM resulting in the above crash.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">That's not all, using React Portals also causes recursive &amp; excessive updates to the underlying node view making the editor super slow as the number of custom node views increase (e.g. too many task items in a task list). I haven't investigated why this happens, though.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In any case, the fix was to get rid of React Portals and use a new React 18 <code style="color:#659604">createRoot</code> for every node view. Sounds excessive but trust me, it works much more smoothly. The only loss is that now node views don't share the parent context which isn't a huge deal: the only context we require is the Theme which can be reapplied as necessary.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Before this change, it took 12 seconds to check 100 task items. Now it takes 1 second.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can review the full changes in this PR <a href="https://github.com/streetwriters/notesnook/pull/5028" target="_blank" class="c_accent">#5028</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other fixes</h3>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed moving notebook on iOS (<a href="https://github.com/streetwriters/notesnook/commit/43fac7901" target="_blank" class="c_accent">43fac7901</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed scrolling broken in reminder -&gt; repeat -&gt; month (<a href="https://github.com/streetwriters/notesnook/commit/167c54578" target="_blank" class="c_accent">167c54578</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed theme search returns empty results (<a href="https://github.com/streetwriters/notesnook/commit/c8c75d40d" target="_blank" class="c_accent">c8c75d40d</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed footer size for notebook screen (<a href="https://github.com/streetwriters/notesnook/commit/bace90808" target="_blank" class="c_accent">bace90808</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed notebook-sheet position and flicker (<a href="https://github.com/streetwriters/notesnook/commit/fe7c7e8db" target="_blank" class="c_accent">fe7c7e8db</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed selection header (<a href="https://github.com/streetwriters/notesnook/commit/716239abf" target="_blank" class="c_accent">716239abf</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed vault unlock with biometrics (<a href="https://github.com/streetwriters/notesnook/commit/62dd5a541" target="_blank" class="c_accent">62dd5a541</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed search bar position on iOS (<a href="https://github.com/streetwriters/notesnook/commit/96558c5df" target="_blank" class="c_accent">96558c5df</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed confusing settings/sync button (<a href="https://github.com/streetwriters/notesnook/commit/95b511407" target="_blank" class="c_accent">95b511407</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed back navigation from editor (<a href="https://github.com/streetwriters/notesnook/commit/20742e2a1" target="_blank" class="c_accent">20742e2a1</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed opening internal link in readonly mode (<a href="https://github.com/streetwriters/notesnook/commit/d77c09f1f" target="_blank" class="c_accent">d77c09f1f</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed position of context menu (<a href="https://github.com/streetwriters/notesnook/commit/aa602ea8e" target="_blank" class="c_accent">aa602ea8e</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed sidebar order not loading on app launch (<a href="https://github.com/streetwriters/notesnook/commit/404d136a7" target="_blank" class="c_accent">404d136a7</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed placeholder showing always in first line of editor (<a href="https://github.com/streetwriters/notesnook/commit/cf9aeecd7" target="_blank" class="c_accent">cf9aeecd7</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed many papercut UX issues in search replace (<a href="https://github.com/streetwriters/notesnook/commit/1adda96ce" target="_blank" class="c_accent">1adda96ce</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed middle pane size increasing on exiting focus mode (<a href="https://github.com/streetwriters/notesnook/commit/b8c68b516" target="_blank" class="c_accent">b8c68b516</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed position of image preview toolbar (<a href="https://github.com/streetwriters/notesnook/commit/249600489" target="_blank" class="c_accent">249600489</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed navigation menu resize issues (<a href="https://github.com/streetwriters/notesnook/commit/781d09dac" target="_blank" class="c_accent">781d09dac</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed toggles in properties not working (<a href="https://github.com/streetwriters/notesnook/commit/2ea9b0486" target="_blank" class="c_accent">2ea9b0486</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed <code style="color:#ce16e2">database disk image is malformed</code> error on updating deleted content (<a href="https://github.com/streetwriters/notesnook/commit/5dbabc270" target="_blank" class="c_accent">5dbabc270</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed various editor crashes (<a href="https://github.com/streetwriters/notesnook/commit/d85974ed7" target="_blank" class="c_accent">d85974ed7</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed wrong version in update sheet (<a href="https://github.com/streetwriters/notesnook/commit/9598ea754" target="_blank" class="c_accent">9598ea754</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed multi-selection UX (<a href="https://github.com/streetwriters/notesnook/commit/42048ad53" target="_blank" class="c_accent">42048ad53</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed crash during backup (<a href="https://github.com/streetwriters/notesnook/commit/1fbd2efb9" target="_blank" class="c_accent">1fbd2efb9</a>)</li>
</ul>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Introducing Notesnook v3]]></title>
            <link>https://blog.notesnook.com/introducing-notesnook-v3</link>
            <guid isPermaLink="false">https://blog.notesnook.com/introducing-notesnook-v3</guid>
            <pubDate>Mon, 29 Apr 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[From August 2023 to April 2024, after 8 months, 1000+ commits, and countless sleepless nights v3 is finally here — the biggest, most feature packed release in the history of Notesnook.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">From August 2023 to April 2024, after 8 months, 1000+ commits, and countless sleepless nights v3 is finally here — the biggest, most feature packed release in the history of Notesnook.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In this release we have rewritten the core architecture of Notesnook, introducing a new Sync Engine, migrating to SQLite, adding support for internal note links, tabs and nested notebooks, alongwith countless big and small improvements to the UX. v3 is the rebirth of Notesnook as a more mature, more stable note taking experience.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There's a lot to talk about in this release but let's start with the new Notesnook logo.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">The new Notesnook logo</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When we started out building Notesnook, our logo was the literal visual representation of "notes" and "nook". That concept quickly got outdated after rapid iteratation that completely changed how Notesnook functioned. Despite all the changes, though, we are still engineers at heart which means we think of everything in terms of "problems" and "solutions". The old logo had a couple of problems:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">It wasn't legible in small sizes (e.g. favicons, tray icons, status bar icons etc.)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">It couldn't be themed</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">It didn't work well with different colors</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">It had a very busy design</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The new logo solves all of these problems <em>and</em> it looks great.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">&nbsp;</p>
<div class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/new-notesnook-logo.COckGXje.webp 531w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/new-notesnook-logo.CRHYo22p.png 531w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/new-notesnook-logo.CRHYo22p.png" srcset="https://blog.notesnook.com/assets/static/new-notesnook-logo.CRHYo22p.png 531w" sizes="100vw" alt="The new Notesnook logo" style="max-width:531px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">The new Notesnook logo</figcaption></figure><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/themed-notesnook-logo.-leGE8JO.webp 640w, https://blog.notesnook.com/assets/static/themed-notesnook-logo.DPQmd2tE.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/jpg" srcset="https://blog.notesnook.com/assets/static/themed-notesnook-logo.BSuz5Zru.jpg 640w, https://blog.notesnook.com/assets/static/themed-notesnook-logo.CYZVEtbl.jpg 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/themed-notesnook-logo.BSuz5Zru.jpg" srcset="https://blog.notesnook.com/assets/static/themed-notesnook-logo.BSuz5Zru.jpg 640w, https://blog.notesnook.com/assets/static/themed-notesnook-logo.CYZVEtbl.jpg 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Material UI Themed Logo" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Material UI Themed Logo</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">&nbsp;</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Migrating to SQLite</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you have been using Notesnook you would never notice that all of it is built on a very simple key value database. KV databases are really cool but fail miserably if you want to selectively query data, or do partial updates. As a result, when you increase the number of notes in Notesnook, the performance becomes abysmal. I am talking about 30+ seconds to startup the app with 50K notes on iPhone 6s, laggy scrolling, and a simple note taking 10s to save on each edit.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Since this only occured for 20K+ notes, we could afford to be slow for some time but sooner or later we needed a permanent solution. We had the following options:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Optimize the slow parts of the database (essentially building our own database &amp; query engine) and continue shipping.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Migrate to SQLite</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Building your own database engine is really not a good idea. I wish we had seen this while prototyping Notesnook because while choosing a KV database sped up development it forced us to repay that cost later on. Using the right tool for the job is crucial for any serious software development.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To decide our next steps, we set down a few targets:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The clients should perform predictably regardless of how much data you have. Loading 1000 notes should take the same amount of perceivable time as loading 50K notes (i.e. using lazy loading, pagination etc.)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The clients should use as little RAM as possible to work in extremely constrained enviroments (e.g. our web clipper shouldn't crash on iOS which limits all Share Extensions to around 50mb of RAM).</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Instant startup times regardless of how many notes a user has.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Future proof (i.e. we shouldn't need to migrate again)</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And a couple of good to haves:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Scrolling a list with 50K notes at 60 FPS</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Improved search</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Faster saving of notes</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Keep the above in mind, the choice was obvious: it was time to migrate to SQLite.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>In v3, you will notice the following improvements immediately:</strong></p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Instant startup even with 50K+ notes (1.5s vs 30s on iPhone 6s)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Lag free scrolling &amp; navigation</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Exporting notes &amp; taking backups should be faster</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Search should be faster and more accurate</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Faster sync (thanks to the power of SQL)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Web clipper on mobile should no longer crash if you have a lot of notes</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">After migration, the next obvious step was fixing the sync. Specifically we wanted to fix one of the longest standing issue with Notesnook Sync: a change you make on one device sometimes wouldn't sync to other devices and vice versa.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Fixing the sync</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">From the beginning, Notesnook has had a decentralized sync system where each client can independently sync items without depending on a central server. This could only work based on time, of course, but it also had a few drawbacks:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Since each client was independent, it was a nightmare to debug <em>why</em> something wasn't syncing.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">In order to be efficient, we detected changed items based on their modified timestamp but this forced us to juggle with the various inconsistencies of time on different devices.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Building everything on top of timestamps meant resumability during sync could never be stable.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As you can imagine, in order for a time based sync system to work without issues, time must be the same on all devices at all times. What happens if one device is ahead/behind by a few milliseconds? Items get missed out of the sync cycle, and you have to run a force sync (which sends &amp; fetches all the items instead of only the changed ones).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It was clear that to properly fix the sync we had to get rid of the time factor. Turns out a centralized architecture is the best way to solve this problem:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Each item has a boolean <code style="color:#e5067d">synced</code> property that becomes <code style="color:#e50ba0">false</code> whenever it is changed.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">When pushing changes, we just get all the items with <code style="color:#e5067d">synced</code> set to <code style="color:#e50ba0">false</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The server keeps a list of all unsynced items for each device, and when a device runs the sync it only sends those items.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This fixes all the problems with a time based sync engine:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Debugging is easier because server has a list of all changes for each device.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Detecting changed items is just as efficient as comparing their modified time but more stable.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Resumability comes built-in.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>As a result, in v3 you'll notice a much more reliable sync that "just works" regardless of where you are, how many devices you are on, or the time difference between them.</strong></p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">Background sync on mobile</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook mobile apps will also now automatically sync your notes and other data in the background at regular intervals. This will ensure that you are always synced even if you never open the app. For example, setting a reminder on the desktop/web app will automatically set it on your phone as well (without requiring you to open the app).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>Background sync can be disabled from Settings &gt; Sync Settings &gt; Background sync.</em></p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">Merge conflict tolerant sync</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In v2, whenever a merge conflict occured the sync would stop until you resolved all the conflicts regardless of whether you made changes in the conflicted note(s) or not. This was quite frustrating.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In v3, we have fixed this: the app will now continue to sync even if you have merge conflicts.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Features, Features</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It is always a struggle to decide which features to add and which features to leave for later. v3 is no different but even then, it is probably one of our biggest releases ever.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Note linking</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The Crown Jewel of v3!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">After a long excrutiating wait we have finally added support for linking 2 notes in Notesnook. Not just that, you can also link directly to a specific block inside a note. Clicking on the internal link takes you directly to that block.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/80263f24f994ce49-image.CrUtU32y.webp 382w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/80263f24f994ce49-image.C5Uq2Y1B.png 382w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/80263f24f994ce49-image.C5Uq2Y1B.png" srcset="https://blog.notesnook.com/assets/static/80263f24f994ce49-image.C5Uq2Y1B.png 382w" sizes="100vw" alt="Link directly to a specific block inside a note." style="max-width:382px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Link directly to a specific block inside a note.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We also added controls to quickly see which notes are currently linked, and which other notes are referencing a particular note.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/9b38cc4ee523e0d4-image.1Mn6V4Dx.webp 207w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/9b38cc4ee523e0d4-image.fZZ2Gup0.png 207w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/9b38cc4ee523e0d4-image.fZZ2Gup0.png" srcset="https://blog.notesnook.com/assets/static/9b38cc4ee523e0d4-image.fZZ2Gup0.png 207w" sizes="100vw" alt="Quickly see which notes are currently linked." style="max-width:207px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Quickly see which notes are currently linked.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To keep things simple, a note link is no different than a normal hyperlink except that clicking on it will take you to the note instead of an external page. When you export your notes, your note links are automatically resolved to actual Markdown/ HTML files. This is something no other app (that I know of) does instead keeping the links in their app specific format (with note id and everything) making them essentially useless after export.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Tabs</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The only reason we added tabs was to allow quick navigation to/from a note on clicking a note link. You can, of course, use tabs without ever linking a note.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/4634baf979d46daf-image.SBzQfp3L.webp 441w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/4634baf979d46daf-image.C5xfWmOH.png 441w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/4634baf979d46daf-image.C5xfWmOH.png" srcset="https://blog.notesnook.com/assets/static/4634baf979d46daf-image.C5xfWmOH.png 441w" sizes="100vw" alt="Tabs in Notesnook." style="max-width:441px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Tabs in Notesnook.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook is one of the few note taking apps (besides Obsidian &amp; OneNote) that has a full fledged tab experience. With v3, we are slowly upgrading Notesnook to become a poweruser's tool while keeping things just as simple as before. Which brings me to...</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Nested notebooks</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With v3, we are getting rid of "topics" as an organization concept, and replacing it with "subnotebooks". All your topics will automatically convert to subnotebooks after upgrading to v3.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Subnotebooks function similar to a topic except that you can have unlimited levels of subnotebooks. For example, a structure like this was not possible in v2:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code>- Blogs
  - Programming
    - JavaScript
    - Python
    - C++
  - Privacy
    - Notesnook
    - Android
      - GrapheneOS
      - Other OSs
</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Complex structures like this are now possible in v3, and they work just like they did in v2 so you'll feel right at home.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">At rest encryption</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook was always end-to-end encrypted i.e. your notes always got encrypted on your device before being sent to the cloud. However, those notes weren't stored encrypted <em>on your device</em>. For some users, this was a big no-no.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With v3, we are fixing this short coming of Notesnook. Thanks to SQLite (and sqlite-multiple-ciphers), at rest encryption is now a thing on all platforms. When you first open the app, it will automatically generate an encryption key and store it securely in the platform KeyStore/KeyChain. This encryption key is then used to encrypt/decrypt the SQLite database.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In other words, it "just works" without any intervention required on your part. All sensitive data is encrypted <em>except</em>:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">App logs</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">On-device settings &amp; configuration</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">App lock</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">At rest encryption works with or without app lock, but it is <strong>recommended</strong> that you enable it nonetheless especially on the web app where there is no platform KeyChain/KeyStore.</p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">Web/Desktop</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When you enable app lock, it further encrypts your database encryption key with your app lock pin (or security key) so it's not just an "overlay". If you forget your app lock pin, the only way into the app is to reset &amp; clear the database and start over.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/app-lock-settings.D4ts4zZS.webp 640w, https://blog.notesnook.com/assets/static/app-lock-settings.CH3KGEv_.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/app-lock-settings.DBC5h-TE.png 640w, https://blog.notesnook.com/assets/static/app-lock-settings.CuK17RuS.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/app-lock-settings.DBC5h-TE.png" srcset="https://blog.notesnook.com/assets/static/app-lock-settings.DBC5h-TE.png 640w, https://blog.notesnook.com/assets/static/app-lock-settings.CuK17RuS.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Setting up app lock in Notesnook" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Setting up app lock in Notesnook</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can also setup app lock with your hardware security key (e.g. YubiKey/SoloKey) on Chromium-based browsers &amp; the desktop app. Firefox &amp; Safari, unfortunately, still do not support the <code style="color:#b708ce">prf</code> extension which is required for this to work.</p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">iOS/Android</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">On iOS/Android, the app lock is independent of at rest encryption i.e. setting up app lock doesn't further encrypt your database encryption key with the app lock credential (e.g. password/biometrics). This is done intentionally due to the following limitations:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Background sync cannot work if app lock is turned on.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Snoozing/dismissing of reminders becomes impossible because the database cannot be opened without asking the user for the app lock credential.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Web clipper on mobile cannot work for similar reasons.</li>
</ol>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/app-lock-settings-mobile.w4BXdoxh.webp 400w, https://blog.notesnook.com/assets/static/app-lock-settings-mobile.DWAkyj_J.webp 600w, https://blog.notesnook.com/assets/static/app-lock-settings-mobile.JAY64wDs.webp 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/app-lock-settings-mobile.3ydxPfGg.png 400w, https://blog.notesnook.com/assets/static/app-lock-settings-mobile.CGR5SYhO.png 600w, https://blog.notesnook.com/assets/static/app-lock-settings-mobile.CFdZemGM.png 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/app-lock-settings-mobile.3ydxPfGg.png" srcset="https://blog.notesnook.com/assets/static/app-lock-settings-mobile.3ydxPfGg.png 400w, https://blog.notesnook.com/assets/static/app-lock-settings-mobile.CGR5SYhO.png 600w, https://blog.notesnook.com/assets/static/app-lock-settings-mobile.CFdZemGM.png 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Setting up app lock in Notesnook" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Setting up app lock in Notesnook</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">However, this is not a huge concern because the database encryption key is already stored very securely in your phone's KeyStore/KeyChain. In the future, we plan on working around these limitations or offering a "Super Secure" mode as an opt-in feature on mobile to disable these extra features.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Export attachments with notes</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In v3, we have improved the export process to include:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Attachments are now exported as separated files (both files &amp; images) instead of getting embedded inside notes.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Notes are automatically organized based on the notebooks they are in.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Internal note links are resolved to actual HTML/MD files</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Notes &amp; notebooks with duplicate names are now properly renamed for deduplication.</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">User profile</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In v2 we added the ability to personalize Notesnook using themes and in v3 we are taking that a step further by allowing you to set up your profile. This will add a personal touch to your Notesnook experience. As always, everything is 100% end-to-end encrypted included the profile picture.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/3ee3d763001f80c1-image.NVmXQ4JM.webp 205w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/3ee3d763001f80c1-image.D2IOBige.png 205w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/3ee3d763001f80c1-image.D2IOBige.png" srcset="https://blog.notesnook.com/assets/static/3ee3d763001f80c1-image.D2IOBige.png 205w" sizes="100vw" alt="User profile in Notesnook" style="max-width:205px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">User profile in Notesnook</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Currently your profile is only used for aesthetic purposes. However, in the future we plan to make use of it for Monographs (optionally), collaboration etc.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Custom colors for note organization</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Each person has their own color hierarchy. For some, red signifies importance but for others it has a different meaning. Some people can't tolerate a certain shade of red etc. etc. That is why in v3, we are introducing custom colors for organizing your notes.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/colors.CzMT2QAj.webp 178w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/colors.BO1AYd2y.png 178w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/colors.BO1AYd2y.png" srcset="https://blog.notesnook.com/assets/static/colors.BO1AYd2y.png 178w" sizes="100vw" alt="Custom colors in Notesnook" style="max-width:178px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Custom colors in Notesnook</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can add as many colors as you like with whatever shade you like. No 2 colors can have the same shade, however.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Customizable side bar</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The side bar is now fully customizable including:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Hiding items you don't use</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Changing the order of items (e.g. move Notebooks to the top)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Reorganizing the order of shortcuts &amp; colors</li>
</ol>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/sidebar-customization.yw_6lZRz.webp 382w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/sidebar-customization.nKj_mB6Z.png 382w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/sidebar-customization.nKj_mB6Z.png" srcset="https://blog.notesnook.com/assets/static/sidebar-customization.nKj_mB6Z.png 382w" sizes="100vw" alt="Customizable side bar" style="max-width:382px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Customizable side bar</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It can be as simple or as complex as you want.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Callouts</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With callouts you can make certain sections of your notes "pop" for better readability. Initially, we have added around 20 types of different callouts which should suffice.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Callouts are also collapsible and nestable so you can add a callout inside a callout etc.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/callouts.DgebWwjU.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/callouts.DxfJzde2.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/callouts.DxfJzde2.png" srcset="https://blog.notesnook.com/assets/static/callouts.DxfJzde2.png 640w" sizes="100vw" alt="Callouts in Notesnook editor" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Callouts in Notesnook editor</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Adding a new callout is as simple as:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code>&gt;info Callout Title
</code></pre></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Table of Contents</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you have very long notes with a lot of headings, navigating around can become really painful. In v3, we have added support for Table of Contents so you can quickly jump around without a lot of scrolling.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/table-of-contents.DR7pmem_.webp 640w, https://blog.notesnook.com/assets/static/table-of-contents.E5bMaJqD.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/table-of-contents.BC5uOr6J.png 640w, https://blog.notesnook.com/assets/static/table-of-contents.BtsqE4P0.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/table-of-contents.BC5uOr6J.png" srcset="https://blog.notesnook.com/assets/static/table-of-contents.BC5uOr6J.png 640w, https://blog.notesnook.com/assets/static/table-of-contents.BtsqE4P0.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Table of contents" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Table of contents</figcaption></figure>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Custom titlebar on Desktop app</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A desktop app should look and feel like a desktop app instead of just a wrapper around a web app. In v3 we are bringing a fully custom title bar with integrated editor tabs to make things feel more "desktop-y".</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/custom-titlebar.B7JTmuVa.webp 640w, https://blog.notesnook.com/assets/static/custom-titlebar._UPrPxXI.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/custom-titlebar.7P3OAl5m.png 640w, https://blog.notesnook.com/assets/static/custom-titlebar.BCiEs6Ka.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/custom-titlebar.7P3OAl5m.png" srcset="https://blog.notesnook.com/assets/static/custom-titlebar.7P3OAl5m.png 640w, https://blog.notesnook.com/assets/static/custom-titlebar.BCiEs6Ka.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Custom title bar on desktop" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Custom title bar on desktop</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With this change, we are making more and more use of the native capabilities of the platform. Even though the web and desktop apps feel similar in functionality, their underlying implementations vary a lot:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Desktop app has native encryption, hashing &amp; compression making sync and other things quite a bit faster.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Desktop app supports automatic backups that get saved to a folder on your disk.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">At rest encryption keys are stored in the platform KeyStore/KeyChain in the desktop app, making it much more secure.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">System tray integration only exists in the desktop app</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">And many other things that really makes a difference.</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Change highlighting in merge conflicts</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you get a lot of merge conflicts, or if you are just comparing 2 note versions, it can become really difficult to figure out <em>what</em> actually changed. In v3, we have added change highlighting on all platforms when viewing merge conflicts or note versions.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/diff.BlSbXEmp.webp 640w, https://blog.notesnook.com/assets/static/diff.DsOWRCDz.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/diff.C82qpKqt.png 640w, https://blog.notesnook.com/assets/static/diff.V9CgqCrF.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/diff.C82qpKqt.png" srcset="https://blog.notesnook.com/assets/static/diff.C82qpKqt.png 640w, https://blog.notesnook.com/assets/static/diff.V9CgqCrF.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Change highlighting in merge conflicts" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Change highlighting in merge conflicts</figcaption></figure>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Image compression &amp; multi image upload</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Ever since we added support for attachments, we have avoided compressing images to allow users to attach images in their full quality. The drawback of this approach is that on some slower platforms like mobile, loading notes with a lot of images becomes extremely slow and laggy. Another point to consider is that not all images need to be added in their original quality (e.g. receipts, screenshots etc.).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To fix this performance issue, we are now enabling image compression by default with a toggle to disable it for specific images.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/image-compression-multi-images.B90ITSWp.webp 431w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/image-compression-multi-images.D-cx9QwZ.png 431w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/image-compression-multi-images.D-cx9QwZ.png" srcset="https://blog.notesnook.com/assets/static/image-compression-multi-images.D-cx9QwZ.png 431w" sizes="100vw" alt="You can now attach multiple images at once (on all platforms)." style="max-width:431px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">You can now attach multiple images at once (on all platforms).</figcaption></figure>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other features</h3>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Add support for copy/pasting code blocks with syntax highlighting.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The top bar in note properties sheet now remembers which actions to take more frequently and moves the to top automatically. For example if you use the "Copy" action a lot, it automatically moves to start. We have also moved most toggles and single click actions to top bar from the bottom grid.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Simple checklists</strong> — sometimes you just need a simple checkbox without all the fancy task list features. In this beta version we have added just that. Checklists are barebones version of task lists being extremely lightweight and simple to use.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><strong>Drag/drop notes &amp; notebooks</strong> — you can now drag drop notes and notebooks just like you'd do files on your file browser. Drag a note to a notebook, color or tag and it'll automatically get assigned. Or maybe drag a notebook to another notebook and it'll automatically become its child.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">You can now sort/group your notes by both title &amp; date.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We have added a "New Note" Quick Settings button on Android.</li>
</ol>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Get v3 now!</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This release was one of the most stressful releases ever taking over 8 months of development, and countless hours of testing. None of this could ever be possible without the feedback from our beta users. For the last 3 months, our beta users have helped us find countless bugs &amp; inconsistencies, and we could not be more grateful! Thank you to everyone who contributed, helped, motivated, and shared their feedback.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can grab v3 from our <a href="https://notesnook.com/downloads" target="_blank" class="c_accent">official website</a>, and also from the respective app store on your platform.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image._w_DfEmP.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[The Skiff Privacy Fiasco, or How not to Shutdown Your Startup]]></title>
            <link>https://blog.notesnook.com/the-skiff-privacy-fiasco</link>
            <guid isPermaLink="false">https://blog.notesnook.com/the-skiff-privacy-fiasco</guid>
            <pubDate>Sun, 11 Feb 2024 12:01:22 GMT</pubDate>
            <description><![CDATA[On February 9th, 2024 Skiff Privacy discontinued its services. In this blog post, I want to do an informal case study of how Skiff shut down its operation and what they could have done better.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>Disclaimer: This is an opinion piece and not meant to be judgmental, accusatory, or personal. I truly wish the Skiff Privacy founders best of luck in their next venture. This blog post is meant as an informal case study of how Skiff shut down its operation and what they could have done better.</em></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>The reader must keep in mind that Notesnook is <strong>not</strong> a Skiff Privacy competitor, in part or in whole. And as such, this blog post is not meant as a way to take a stab at a competitor. The only part of Skiff that is remotely similar to Notesnook is its Skiff Pages offering, but even that is fundamentally different and solves a very different problem (aimed more as a Google Docs alternative).</em></p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><a href="https://en.wikipedia.org/wiki/Skiff_(email_service)" target="_blank" class="c_accent">Skiff Privacy started out in 2020</a>, publicly launched in 2021-2022, and <a href="https://www.notion.so/blog/meet-skiff-the-newest-member-of-the-notion-family" target="_blank" class="c_accent">shut down in 2024</a>. It was a typical <a href="https://medium.com/sequoia-capital/skiff-changing-the-privacy-paradigm-64dee666fb00" target="_blank" class="c_accent">VC-backed</a> Silicon Valley startup that quickly gained a lot of traction among the privacy community, but failed to appease its VCs and got acquire-hired by Notion.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A very frequent but unanswered question is why shutdown in the first place? In November 2023, <a href="https://twitter.com/skiffprivacy/status/1725969315332067604" target="_blank" class="c_accent">Skiff reported</a> having crossed 2 million users. That sounds like a success but unfortunately, that's not how VC-backed startups work.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The most likely reason for Skiff shutting down is because it has become unviable. You see, Skiff raised around $14 million in seed funding. That's outsider money from VCs which means outside pressure to turn a profit. Since they decided to shutdown, it's obvious that they failed on their promise.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Why Notion, though? It turns out Skiff received funding from Sequoia Capital which is also <a href="https://www.sequoiacap.com/companies/notion/" target="_blank" class="c_accent">an investor in Notion Labs</a>. According to <a href="https://hbr.org/1998/11/how-venture-capital-works" target="_blank" class="c_accent">Harvard Business Review</a>:</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Venture money is not long-term money. The idea is to invest in a company’s balance sheet and infrastructure until it reaches a sufficient size and credibility so that it can be sold to a corporation or so that the institutional public-equity markets can step in and provide liquidity. In essence, the venture capitalist buys a stake in an entrepreneur's idea, nurtures it for a short period of time, and then exits with the help of an investment banker.</p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Of course, startups fail all the time. That's nothing special but the conduct of the founders in how they carry out a shutdown speaks a lot about what happened, why it happened, and what their priorities/motives were. In this blog post, I want to discuss a few things that stood out to me as completely baffling.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">1. Miscommunication</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">On February 9th, 2024, when Skiff decided to stop operations, this is what they put on <a href="https://web.archive.org/web/20240211041555/https://skiff.com/" target="_blank" class="c_accent">their website</a>:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/joining-notion.Bnw4u7QP.webp 640w, https://blog.notesnook.com/assets/static/joining-notion.CPFPaqsn.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/joining-notion.BHrCR5R9.png 640w, https://blog.notesnook.com/assets/static/joining-notion.B_UjIIE0.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/joining-notion.BHrCR5R9.png" srcset="https://blog.notesnook.com/assets/static/joining-notion.BHrCR5R9.png 640w, https://blog.notesnook.com/assets/static/joining-notion.B_UjIIE0.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="We are excited to share the Skiff is joining Notion" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Skiff website on the day they shut down</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><br>
<!-- -->Anyone reading the above would think Skiff is only joining Notion instead of completely shutting down. I mean, they <em>are excited</em> so it must be something good, right? Who is ever excited about shutting down their company? Well, maybe the next few paragraphs would shed more light on the situation?</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/joining-notion-2.Dy1mHtI8.webp 640w, https://blog.notesnook.com/assets/static/joining-notion-2.BYZJmnLl.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/joining-notion-2.BIEoMMfY.png 640w, https://blog.notesnook.com/assets/static/joining-notion-2.DT42uMhs.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/joining-notion-2.BIEoMMfY.png" srcset="https://blog.notesnook.com/assets/static/joining-notion-2.BIEoMMfY.png 640w, https://blog.notesnook.com/assets/static/joining-notion-2.DT42uMhs.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="An excrept from the Skiff website" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">An excrept from the Skiff website</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><br>
<!-- -->No mention of shutting down, no mention of discontinuation, not even a single mention of what happens next. Just some vague corporate jargon about "mission" and joining Notion. Their copy sounds like this: "We are joining hands with Notion to continue building Skiff" when it actually means: "Skiff failed to be viable and is being discontinued. The Skiff team is being acquire-hired by Notion."</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But where do they even say they are discontinuing? Maybe they aren't discontinuing (and I am lying)!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Click on <a href="https://web.archive.org/web/20240211034606/https://skiff.com/data-migration" target="_blank" class="c_accent">"Learn about migrating your data" link</a> (why <em>would</em> a user even click on it if they didn't think Skiff is discontinuing?), scroll down and <strong>expand the FAQ item</strong>:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/shutting-down-1.BBkTssxV.webp 640w, https://blog.notesnook.com/assets/static/shutting-down-1.CybSEM7D.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/shutting-down-1.eZL9YslY.png 640w, https://blog.notesnook.com/assets/static/shutting-down-1.BvtCHGve.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/shutting-down-1.eZL9YslY.png" srcset="https://blog.notesnook.com/assets/static/shutting-down-1.eZL9YslY.png 640w, https://blog.notesnook.com/assets/static/shutting-down-1.BvtCHGve.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="An excrept from the Skiff website" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">An excrept from the Skiff website</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><br>
<!-- -->There it is hidden among paragraphs upon paragraphs of pointless marketing speech. How is a casual user taking just a cursory look at their website supposed to notice and understand this important information? They also sent an email to all their users containing this very letter as if that would give more context?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is nothing new on part of Skiff. Since its inception, Skiff has portrayed itself as a "transparent and completely open source" product lineup:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/open-source-skiff.gl7miOM_.webp 640w, https://blog.notesnook.com/assets/static/open-source-skiff.B_3SpCYA.webp 1024w, https://blog.notesnook.com/assets/static/open-source-skiff.DHkoMx2M.webp 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/open-source-skiff.B2Zrgost.png 640w, https://blog.notesnook.com/assets/static/open-source-skiff.B4DLCq7N.png 1024w, https://blog.notesnook.com/assets/static/open-source-skiff.Dk5lTKxi.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/open-source-skiff.B2Zrgost.png" srcset="https://blog.notesnook.com/assets/static/open-source-skiff.B2Zrgost.png 640w, https://blog.notesnook.com/assets/static/open-source-skiff.B4DLCq7N.png 1024w, https://blog.notesnook.com/assets/static/open-source-skiff.Dk5lTKxi.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Is Skiff really open source?" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Is Skiff really open source?</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><br>
<!-- -->However, if you <a href="https://github.com/skiff-org/skiff-apps" target="_blank" class="c_accent">look a little deeper</a> you'll realize only some parts of Skiff are source available (not open source since <a href="https://github.com/skiff-org/skiff-apps/blob/main/LICENSE" target="_blank" class="c_accent">their license is not OSI approved</a>). For example, <a href="https://github.com/skiff-org/skiff-apps/blob/main/skemail-web/README.md" target="_blank" class="c_accent">none of their backend servers are open source</a>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There's nothing wrong with being source available (Signal, Proton Mail are all source available or partially open source), the problem is communication. If you claim in one place that you are completely open source then you also have to back it up. This issue was brought up a <a href="https://web.archive.org/web/20230804213047/https://github.com/skiff-org/skiff-apps/issues/93" target="_blank" class="c_accent">few</a> <a href="https://web.archive.org/web/20240210180403/https://github.com/skiff-org/skiff-apps/issues/94" target="_blank" class="c_accent">times</a> over the years by different users but nothing ever changed. They neither changed their marketing copy nor their license.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">2. Causing a panic</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">What would you do if you received an email about a mission critical (because email is pretty mission critical) product getting shut down? You'd go to the community forum and ask around to know what's going on, what happened, and what to do next.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">How would you feel if you opened Discord and the product's <a href="https://discord.gg/skiff" target="_blank" class="c_accent">official Discord server</a> was gone? You go to their GitHub repo and the GitHub repo is also gone. You open their Twitter/X page but find no clear information about what's going on except "excitement" around joining Notion and a link to a pointless page that tells absolutely nothing. It's evident that you will panic.</p>
<div style="display:flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/skiff-github-before.D68ncrY0.webp 631w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/skiff-github-before.FGYPeIAe.png 631w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/skiff-github-before.FGYPeIAe.png" srcset="https://blog.notesnook.com/assets/static/skiff-github-before.FGYPeIAe.png 631w" sizes="100vw" alt="Skiff website on the day they shut down" style="max-width:631px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Skiff's GitHub repo on 2024-02-03<!-- --> <!-- -->-<!-- --> <a href="https://web.archive.org/web/20240203021434/https://github.com/skiff-org/skiff-apps" class="c_info">Source</a></figcaption></figure><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/skiff-github-after.C5bpr343.webp 628w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/skiff-github-after.DDOmv1w9.png 628w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/skiff-github-after.DDOmv1w9.png" srcset="https://blog.notesnook.com/assets/static/skiff-github-after.DDOmv1w9.png 628w" sizes="100vw" alt="Skiff website on the day they shut down" style="max-width:628px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Skiff's GitHub repo on 2024-02-11. Notice how the stars on the repo are so few. That can only happen after making a private repo public.<!-- --> <!-- -->-<!-- --> <a href="https://web.archive.org/web/20240211034628/https://github.com/skiff-org/skiff-apps/" class="c_info">Source</a></figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Here's a message we received in our own Discord server from a Skiff user:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/skiff-user-discord-message.B_IdwG7a.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/skiff-user-discord-message.CTet1Y4V.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/skiff-user-discord-message.CTet1Y4V.png" srcset="https://blog.notesnook.com/assets/static/skiff-user-discord-message.CTet1Y4V.png 640w" sizes="100vw" alt="Skiff website on the day they shut down" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Skiff website on the day they discontinued</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><br>
<!-- -->There are countless such examples of Skiff user's panicking all over Twitter/X. This panic could easily have been avoided through a more transparent and open process.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">3. Lack of empathy</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I encourage you to read all of Skiff's messaging around their shutting down. You'll notice an absurdly jolly and excited tone ("we are excited...", "...accelerate our mission...", "...next stage of our journey..." etc.) that completely ignores the frustration and disappointment its users are going through. Hidden in all that corporate lingo and marketing jargon you'll scarcely find a genuine apology.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There is a stark contrast between the jubilant tone of Skiff vs. the disappointment and frustration of its users. It almost feels as if Skiff is happy to screw over its userbase, and "excited" to move on instead of understanding and supporting the community (not saying that's the case but it does sound like it).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The 6 month deadline on top of that adds additional uncertainty that could have been avoided through better and clearer communication. If ever there was a time to be positive, this wasn't it. That's like laughing and joking in the face of your neighbor while their house burns down.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Conclusions</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As of 11th February, 2024, the @skiffprivacy Twitter/X account is going around claiming that Skiff's open source repos are still accessible and available (after they already took them down once) ignoring the fact that frontend without the backend is useless.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/skiff-twitter-1.DI2yL4rx.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/skiff-twitter-1.Dp5Gmvum.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/skiff-twitter-1.Dp5Gmvum.png" srcset="https://blog.notesnook.com/assets/static/skiff-twitter-1.Dp5Gmvum.png 640w" sizes="100vw" alt="Skiff website on the day they shut down" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">From @skiffprivacy<!-- --> <!-- -->-<!-- --> <a href="https://twitter.com/skiffprivacy/status/1756462432166482171" class="c_info">Source</a></figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Product shutdowns do occur but if any service is going to shut down, they should keep the following in mind:</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Don't lie to your users</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Startups fail all the time. That is the nature of startups. But if you are failing, don't try to make it look like a win. Fancy words cannot hide the truth. No one discontinues and shuts down because they "won". Just be honest and communicate clearly. Don't give false hopes like "we are looking forward to...", "...excited about the next stage" etc. That's just pointless and frankly, no one cares what you do after shutting down. It's your product that people care about.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Telling users the why and what goes a long way in establishing trust. Don't keep them in the dark because you are too scared to own up to your mistakes. Communication and transparency around the whole process is essential (especially if you subscribe to the "privacy and freedom" ideology).</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Show empathy and face the backlash</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When MYKI shut down a few years back, it was one of the most frustrating times for me. No other alternative was satisfactory. I tried Bitwarden, 1Password, LastPass, and all sorts of password managers but none of those came even close (I eventually migrated to Keepass).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Migrating from one tool to another is not a trivial task so show empathy and understand your users. If they are angry, their anger is genuine. If they are frustrated, their frustration is genuine. Don't leave them hanging and helpless. It's not the users' fault that you have to shut down so don't walk all over them.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Running a startup is not easy, and the more users you have the worse the backlash will be when you shutdown but facing the backlash head-on is part of this whole business. You cannot run away so just be as honest, as transparent, and as open as possible.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Good faith gestures</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">All startups shutting down should open source all their code. Period.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It benefits no one when a startup just blinks out of existence. There are so many examples of really good and useful products that solved real, actual problems but had to close down. It would be phenomenal if all those services published their code before going dark so someone else could take up the mantle.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Any startup genuine about its claims of "mutual benefit" should open source all the code under a permissible license for the sake of public good. If that cannot be done then mass refunds should be issued for all paying customers. It shouldn't be an option given to users but a proactive action done on the part of the startup. If your users trusted you enough to purchase your subscription, the least you can do is a issue a refund when you shut down.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Of course, none of this is enough to placate a frustrated user but it goes a long way in showing that you really did try your best. Otherwise, it just looks like you are swindling people's time and money.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Give your users enough time to migrate</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Not all users can migrate in a blink. Many users don't even have the mental capacity to think about migration. You are not only responsible for letting users export their data but also for helping them find and migrate to good trusted alternatives. After all, they trusted you with their time, money, and data so you should return the same courtesy.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">How much time is enough time is different for each product but I think establishing an extra margin of a year or two really helps dispel all the panic around the shutdown. That sounds like a lot but a few weeks or months is not enough time to migrate. All it does is create panic while users scramble to find a good enough alternative.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Final thoughts</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I wish Skiff didn't shutdown. Just a few weeks back I was exploring it in hopes of migrating our business email to Skiff. They created something really great, and it's unfortunate that they have to discontinue.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If any of the founders from Skiff Privacy is reading this post, I encourage you to open source the backend as well as everything else around Skiff. That'll leave a legacy behind instead of the bad feelings users have currently.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">That's it from me. Thank you for reading. If you have any questions, feel free to reach out via email, or on Twitter/X.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Privacy</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.DJzYVLHF.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[What's new in v2.6.0]]></title>
            <link>https://blog.notesnook.com/whats-new-in-2-6-0</link>
            <guid isPermaLink="false">https://blog.notesnook.com/whats-new-in-2-6-0</guid>
            <pubDate>Wed, 09 Aug 2023 12:01:22 GMT</pubDate>
            <description><![CDATA[Custom themes, improved copy/pasting, more reliable attachments uploading and more!]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Many weeks in waiting, the v2.6.0 release brings one of the most amazing features so far: <strong>Custom themes</strong> along with a bunch of other UX improvements &amp; bug fixes.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Read on to find out everything that's new in v2.6.0 and what we will be working on in the coming months.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Custom themes</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Imagine opening Notesnook and wanting it to look a certain way. Maybe you'd like the left navigation bar to be black with green text on it, maybe your theme is beige and you want everything too look just right. Whatever the case, now you can do just that and more.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Before you cry out in horror imagining how much work something like that would require, I want to give you a "sneak peak" at the potential our theming engine has.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/monokai.HDu8WSse.webp 640w, https://blog.notesnook.com/assets/static/monokai.Cx9pvlzR.webp 1024w, https://blog.notesnook.com/assets/static/monokai.CPldL-j1.webp 1600w, https://blog.notesnook.com/assets/static/monokai.DPQPcbN6.webp 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/monokai.DuK7vkd9.png 640w, https://blog.notesnook.com/assets/static/monokai.BGDCEpfl.png 1024w, https://blog.notesnook.com/assets/static/monokai.QwTpwWD3.png 1600w, https://blog.notesnook.com/assets/static/monokai.zO0W_fYU.png 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/monokai.DuK7vkd9.png" srcset="https://blog.notesnook.com/assets/static/monokai.DuK7vkd9.png 640w, https://blog.notesnook.com/assets/static/monokai.BGDCEpfl.png 1024w, https://blog.notesnook.com/assets/static/monokai.QwTpwWD3.png 1600w, https://blog.notesnook.com/assets/static/monokai.zO0W_fYU.png 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Monokai theme on Notesnook" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Notesnook styled using the famous Monokai theme.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><br>
<!-- -->Normally, if you want to change how something looks you'd be required to have the arcane knowledge of CSS. That's how Obsidian's does it (and there's nothing wrong with that). However, there's the obvious problem of not everyone knowing how to work around in CSS.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">That is why we built Notesnook's theming engine to be as simple as possible. If you know colors, you know how to build a theme for Notesnook. For example, the above Monokai theme is just <a href="https://github.com/streetwriters/notesnook-themes/blob/main/themes/monokai/v1/theme.json" target="_blank" class="c_accent">a 5 KB JSON file</a> containing around 145 lines in total.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The best part of all this is that it "just works" on all platforms. A single theme can be used, as is, across mobile, web, desktop, and the web clipper. How cool is that!</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">The Theme Builder</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But JSON is also quite complex too, you might say, and you'd be right. That's why we created the Theme Builder. It's a 100% GUI way of building themes for Notesnook. Here's a quick sneak peak.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/theme-builder.C4zwb5kB.webp 640w, https://blog.notesnook.com/assets/static/theme-builder.Czq98-qO.webp 1024w, https://blog.notesnook.com/assets/static/theme-builder.CB4qMFmh.webp 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/jpg" srcset="https://blog.notesnook.com/assets/static/theme-builder.Ca82Z-SB.jpg 640w, https://blog.notesnook.com/assets/static/theme-builder.n4Op3iLS.jpg 1024w, https://blog.notesnook.com/assets/static/theme-builder.DVIGCkB5.jpg 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/theme-builder.Ca82Z-SB.jpg" srcset="https://blog.notesnook.com/assets/static/theme-builder.Ca82Z-SB.jpg 640w, https://blog.notesnook.com/assets/static/theme-builder.n4Op3iLS.jpg 1024w, https://blog.notesnook.com/assets/static/theme-builder.DVIGCkB5.jpg 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="The Notesnook Theme Builder" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Me tweaking the Monokai theme in the Theme Builder.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><br>
<!-- -->The best thing about the Theme Builder is that it is 100% live &amp; real-time. You don't have to reload the app in order to see the changes you made — they are reflected as soon as you make them.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><img alt="The Notesnook Theme Builder" src="https://blog.notesnook.com/media/whats-new-in-2-6-0/theme-builder-live.gif" class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><figcaption class="w_full ta_center font-style_italic fs_s c_info">Live updating in the Theme Builder.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><br>
<!-- -->You can try it out at <a href="https://theme-builder.notesnook.com" target="_blank" class="c_accent">https://theme-builder.notesnook.com</a>. We also wrote a guide on how to use the Theme Builder, which you can <a href="https://help.notesnook.com/custom-themes/create-a-theme-with-theme-builder" target="_blank" class="c_accent">refer to here</a>.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">What about the old accent colors?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Unfortunately, we had to ditch the old accent colors in favor of themes. But don't worry, we will be adding back all the accent colors as themes very soon.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Are themes going to be pro?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Currently, there's no restriction in place so anyone can use any theme they like but eventually, we will add Pro themes that can only be used</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">How to create your own theme?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have added in-depth documentation and help regarding how to create, preview &amp; test your own theme in your help. You can <a href="https://help.notesnook.com/custom-themes/introduction" target="_blank" class="c_accent">check it out here</a>.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Improved copy/pasting</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A note taking app is nothing if it cannot preserve the formatting of whatever comes into or goes out of it. Previously, copying things from Notesnook would completely destroy its formatting, adding empty lines where there should be none, converting single spaced paragraphs into double spaced ones etc. If you have been a Notesnook user, you already now how frustrating that is.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Well, good news for you then! In this release, we have made a bunch of fixes to the way our copy/pasting works:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Copying a single list item no longer copies it as a list but as a paragraph</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Copying multiple list items no longer add empty lines around each list item</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Copying content to &amp; from Notesnook now follows the <code style="color:#0c8e22">Double spaced paragraph</code> setting.</li>
</ol>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">RTL improvements</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you do a lot of writing in RTL languages (Urdu, Arabic etc.), this release will be your favorite for sure! Why do I say that? Well, take a look yourself:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">You now only need to set the text direction once, and everything else just follows it automatically. Previously, you'd have to constantly align different nodes to the right which was cumbersome and annoying.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The title now automatically changes the text direction to RTL if it detects RTL text. How cool is that?</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">RTL notes in the notes list will now automatically be aligned right-to-left.</li>
</ol>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Syntax highlighting and rendered Math in exports</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you do lots of printing or exporting, good news for you! Notesnook will now automatically syntax highlight all the code blocks and render all the math as LaTeX. Here's how it looks:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/rendered-export.ZAQdPyQA.webp 640w, https://blog.notesnook.com/assets/static/rendered-export.PanL5vHX.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/rendered-export.DyefkZ5o.png 640w, https://blog.notesnook.com/assets/static/rendered-export.BgEMItX4.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/rendered-export.DyefkZ5o.png" srcset="https://blog.notesnook.com/assets/static/rendered-export.DyefkZ5o.png 640w, https://blog.notesnook.com/assets/static/rendered-export.BgEMItX4.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Syntax highlighted code blocks in PDF export" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Syntax highlighted code blocks in PDF export</figcaption></figure>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Keyboard shortcuts in menus</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">While keyboard shortcuts are still not implemented, we are slowly preparing the UI for the big change. Starting from this release, you'll now see the keyboard shorcuts in a few places around the editor.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/shortcuts-in-menus.CWeKxd9r.webp 464w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/shortcuts-in-menus.DXu61rsS.png 464w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/shortcuts-in-menus.DXu61rsS.png" srcset="https://blog.notesnook.com/assets/static/shortcuts-in-menus.DXu61rsS.png 464w" sizes="100vw" alt="Keyboard shortcuts in menus" style="max-width:464px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Keyboard shortcuts in editor menus</figcaption></figure>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Bug fixes &amp; minor improvements</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And of course, what's a new major release without a bunch of bug fixes and QoL improvements? Here's the full list of "worthy" bugs that we fixed:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed issue where entering Monograph password closed the Publish dialog</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Copy button on code blocks will no longer be disabled in readonly mode</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed color picker height when the editor toolbar is at the bottom</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Pressing the <code style="color:#a31510">Tab</code> key in the Title will now move the focus to the editor</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Respect empty paragraph lines when printing or exporting to PDF/HTML</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Don't show "Failed to unlock vault" error when exporting a single unlocked note</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">All input fields now follow the current color scheme</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Prevent unintentional saving when opening a note</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Respect text direction when adding blocks like heading, lists etc.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix "Dynamic module import is disabled" error when logging in on Firefox ESR &amp; Tor Browser</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Prevent scroll jump &amp; selection loss when toggling focus mode</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed issue where caret would keep jumping to the end when making edits in the sticky editor title</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Overhaul the logger implementation to be less IO intensive</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix spell checker on desktop app</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fixed issue where embeds would not render in readonly mode</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Format time &amp; date on reminders according to the format specified in the Settings</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Bring app to front if user tries to start a new instance of Notesnook</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Show progress when restoring large encrypted backups</li>
</ol>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">What's next?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A few urgent things on the schedule are:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Fix how we store &amp; sync tags, colors, and settings.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Improve performance of image attachments.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Roll out first iteration of the new search engine.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Explore note linking.</li>
</ol>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.Du85s8rA.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Introducing Notesnook Importer 2.0]]></title>
            <link>https://blog.notesnook.com/introducing-notesnook-importer-2-0</link>
            <guid isPermaLink="false">https://blog.notesnook.com/introducing-notesnook-importer-2-0</guid>
            <pubDate>Fri, 21 Jul 2023 12:01:22 GMT</pubDate>
            <description><![CDATA[Supporting Evernote web clips, Obsidian file embeds, and a lot of bug fixes.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Usually whenever a 2.0 of something comes out, everything has changed. Tons of new features, new UI, new tools around the new features etc. etc. Not so with our Importer.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The only reason its 2.0 and not 1.8 is because there are a few significant improvements we have made that make a major release worthwhile. Read on to find out!</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Evernote web clips</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook Importer has one of the best (if not the best) support for importing from Evernote. We have handwritten the ENML parser to be both fast &amp; support everything attribute under the sun. In our own tests, importing a 2 GB .ENEX file, containing over 2000 notes, is a breeze.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With this release, we are adding full support for importing your saved web clips from Evernote directly into Notesnook with 100% compatibility. We have added support for the following types of web clips:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Full page</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Simplified article</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Article</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Screenshot</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Bookmark</li>
</ol>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Obsidian Importer</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">While we have supported importing from Markdown since v1.0 of the importer, we have never officially supported the Obsidian Flavored Markdown <strong>until now</strong>. With this release, you can easily and quickly import all your Obsidian Markdown files directly into Notesnook with close to 100% compatibility.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Here's a list of everything we support from Obsidian aside from the usual markdown:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">File embeds (e.g. <code style="color:#b50be0">![[path/to/file.ext]]</code>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Comments (e.g. <code style="color:#ad0146">This is a %%comment%%</code>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Frontmatter/Metadata (used to extract tags, title etc.)</li>
</ol>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Note:</strong> custom markdown syntax included by community or other plugins in Obsidian is not (yet) supported.</p>
</blockquote>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Markdown improvements</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With this release, we have overhauled our old markdown parser, <a href="https://marked.js.org/" target="_blank" class="c_accent">marked</a>, and replaced it with <a href="https://remark.js.org/" target="_blank" class="c_accent">remark</a>. It's support for extensions is truly <strong>remark</strong>able allowing us to quickly add support for various formats and Markdown syntaxes. Here's a list of everything that should now officially work:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Subscript and superscript (<code style="color:#8d09c1">H~2~O</code> and <code style="color:#ba00a1">19^th^</code>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Math block (<code style="color:#930dbc">$$some math$$</code>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Highlights (<code style="color:#9400ba">==highlighted==</code>)</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Softbreaks</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have fixed the softbreak collapsing to correctly render softbreaks in lines. Here's a quick before and after.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For this markdown:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-markdown"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>Paragraphs like this that can span multiple lines
<span class="ln-num" data-num="2"></span>for more awesomeness.</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Before:</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Paragraphs like this that can span multiple linesfor more awesomeness.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>After:</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Paragraphs like this that can span multiple lines for more awesomeness.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Single spacing vs. double spacing</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Previously, our markdown importer was defaulting to double spaced paragraphs. While this worked for some, it annoyed quite a big percentage of users. With this release, the importer is no longer opinionated on this. It'll follow whatever you have configured on Notesnook during imports.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Blank lines in code blocks</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Blanks lines now get properly rendered in code blocks instead of getting removed during imports.</p>
<hr style="margin-top:10px;margin-bottom:10px;border-color:var(--theme-ui-colors-border)">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Aside from that, you can expect the markdown importer to produce much more correct output with fewer unexpected issues.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Improved documentation</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We also spent some time improving the documentation and tutorials on how to import from each different app. There's now a step-by-step import guide for Obsidian &amp; plain text files.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Aside from that, each import guide now also has a <strong>Supported formats</strong> section at the end which lists an up to date version of everything we currently support (and don't support) from that app. This will give you a better idea of what kind of work awaits you after importing (or whether to import or not yet).</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Links to step-by-step import guide</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You will now see a link to a step-by-step guide for each note app you want to import from directly on the Importer:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/guide-link.BmnnhPmx.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/guide-link.3vi20YI-.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/guide-link.3vi20YI-.png" srcset="https://blog.notesnook.com/assets/static/guide-link.3vi20YI-.png 640w" sizes="100vw" style="max-width:640px" class="bdr_default w_full"></picture></figure>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Ending thoughts</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook Importer supports a lot of ways to bring your notes from other apps into Notesnook. In fact, Notesnook is the only note taking app that supports this many apps to import from. That is not an easy feat i.e., there are bound to be broken importers and issues during importing.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">However, that has not prevented us from supporting even more apps so far. As for right now, we are working on 3 new importers:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Upnote</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">OneNote (from a OneNote backup file; no more logging in etc.)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Nimbus note</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I'll post an update here once they are ready.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.KrUK8ZQn.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Getting rid of Lerna]]></title>
            <link>https://blog.notesnook.com/getting-rid-of-lerna</link>
            <guid isPermaLink="false">https://blog.notesnook.com/getting-rid-of-lerna</guid>
            <pubDate>Tue, 18 Jul 2023 12:01:22 GMT</pubDate>
            <description><![CDATA[A story about how and why we got rid of Lerna for package management in the Notesnook monorepo.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook was not always a monorepo with 10s of subprojects. Everything used to be in its own Git repository. While that often became tiresome, it was also simpler in a way.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In October 2022 when we went open source, our beast of choice for the seemingly complicated task of monorepo management was Lerna (and Nx). We had to make a few concessions, learn some new patterns, give up old habits, but it all worked out...eventually.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">What we had not foreseen was the impact it would have on our productivity. The only reason we decided to use Lerna was for its package management in a monorepo structure. At the time it solved the problem of, "How do I install X subproject in Y?" but every day we had to face some new problem requiring us to either work around Lerna or deal with it by changing something else. A few examples:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">It is not a trivial task to use Lerna with <code style="color:#14a509">flatpak-builder</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Sometimes Lerna would refuse to update the package lockfiles. The solution? Delete all the <code style="color:#0f0f0f">node_modules</code> directories and run <code style="color:#d30e98">lerna bootstrap</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">CI would fail because Lerna automatically switched to <code style="color:#b806d3">npm ci</code> which requires the lockfile &amp; <code style="color:#ed079c">package.json</code> to correlate. The solution? Force a lockfile update, either by clearing all the <code style="color:#0f0f0f">node_modules</code> or deleting the lockfiles.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Switching <code style="color:#d504d8">git</code> branches and bootstrapping again was a nightmare. Sometimes dependencies wouldn't install or Lerna would install old versions due to outdated package lockfiles. The solution? Delete <code style="color:#0f0f0f">node_modules</code> directory and run <code style="color:#d30e98">lerna bootstrap</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">There's no way to do <code style="color:#d3009b">npm i</code> in a subproject when you are using Lerna. You have to use <code style="color:#bf1385">lerna add</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#bf1385">lerna add</code> doesn't work well with <code style="color:#ad1152">git:</code> or <code style="color:#9013c1">file:</code> packages requiring you to unnecessarily publish everything to NPM.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">There's no <code style="color:#ef0eac">lerna remove</code> so the only way to uninstall a package is by removing it from the <code style="color:#ed079c">package.json</code>, but most of the time that also requires deleting the whole <code style="color:#0f0f0f">node_modules</code> directory.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://packagephobia.com/result?p=lerna" target="_blank" class="c_accent">Lerna takes 100 MB after install.</a></li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Lerna has forced us to download gigabytes over and over again. Okay, I admit, it's not that bad - there's caching and all but still I shouldn't have to clear out <code style="color:#0f0f0f">node_modules</code> repeatedly just to get package management to work.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I am aware that Lerna is not only meant to be used for package management. In fact, even Lerna had to acknowledge their broken package management, calling it "Legacy", and encourage users to use the package managers' Workspaces feature instead. From v7 onwards, Lerna no longer provides its users with the ability to bootstrap or install a package through it.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/lerna-v7.HL6UhGvM.webp 640w, https://blog.notesnook.com/assets/static/lerna-v7.BU5PI2hq.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/lerna-v7.COnwH9v8.png 640w, https://blog.notesnook.com/assets/static/lerna-v7.CGzrbqn-.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/lerna-v7.COnwH9v8.png" srcset="https://blog.notesnook.com/assets/static/lerna-v7.COnwH9v8.png 640w, https://blog.notesnook.com/assets/static/lerna-v7.CGzrbqn-.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Lerna is getting rid of lerna add and lerna bootstrap commands in v7." class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Lerna is getting rid of lerna add and lerna bootstrap commands in v7.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It's good advice when you are starting a new project, but in an old project that's "working" there's no time to deal with all the issues that come with using Workspaces:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#b913c1">npm</code> workspaces defaults to hoisting all the packages in a workspace to the root <code style="color:#0f0f0f">node_modules</code> directory. I have found no way to turn it off. Hoisting creates a boatload of issues which require more tools to fix, which create a new set of issues, ad infinitum.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#d60eb1">yarn</code> workspaces supports turning off hoisting and was almost perfect, but it failed to install some native Node.js dependencies. I had no time to look into why it broke.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#e80083">pnpm</code> almost worked, but it isn't supported by <code style="color:#14a509">flatpak-builder</code> and also requires some nasty hacks to work with Metro Bundler — the de facto bundler for React Native projects.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ed09a1">bun install</code> doesn't work on Windows.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We needed a tried and tested solution that "just worked". Our requirements:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">It should be possible to do <code style="color:#d3009b">npm i</code> or <code style="color:#c9008d">yarn add</code> or <code style="color:#c10dbe">pnpm install</code> wherever we want.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Bootstrapping should be quick and responsive (i.e., we shouldn't have to see dead progress bars for ages).</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">No learning curve. Everything should be obvious, boring, and simple.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">No hoisting. All packages should be placed in each project's <code style="color:#0f0f0f">node_modules</code> directory. This is widely supported and very stable.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#14a509">flatpak-builder</code> support.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Small &amp; hackable, requiring minimum maintenance.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Easy to bootstrap specific scopes (e.g. only bootstrap the desktop app).</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Reliable cross-platform support.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Zero config files.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">It shouldn't be 100mb in size.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You must be thinking (with a groan), "Here comes another monorepo management tool". You'd be 100% wrong.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Working on Notesnook, I have realized no one wants a simple solution. A 10-page-long feature list is preferred over something that does one thing and does it well — the Unix Philosophy, so to say.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">No. I had no time to deal with a new tool that'd break in 100 different ways. I wanted a solution that worked with our existing codebase without too many changes, so I did what I should have done from the beginning:</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I wrote a script.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">144 lines (including blank lines &amp; comments) and only 3 package dependencies (<code style="color:#c9148d">yargs</code>, <code style="color:#053c60">fast-glob</code> &amp; <code style="color:#bf1383">listr</code>) for nicer DX, doing only one thing: bootstrapping. Here's how it looks:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><img src="https://blog.notesnook.com/media/getting-rid-of-lerna/bootstrap-script.gif" class="d_flex ov_hidden m_0 p_0 ai_center jc_center"></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">What's the benefit? Why go to all this effort? Did it really pay off?</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Our CI run time decreased by 1 minute per job across the board. That sounds little, but it all adds up when that job runs 60 times a day.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We have yet to delete the <code style="color:#0f0f0f">node_modules</code> directory even once, and it has been a month, almost.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We can do <code style="color:#d3009b">npm i</code> anywhere we want without breaking anything.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The perception of speed when running <code style="color:#db009d">npm run bootstrap</code> makes us feel 10x faster.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Lockfiles never get outdated or suddenly updated for no reason.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">It's just <code style="color:#d3009b">npm i</code> doing all the work, so everything works without changes.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Best of all, there's not been one occasion where any of us has screamed with frustration over Lerna getting stuck.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Life is simple again.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Bootstrapping a monorepo is a surprisingly simple task consisting of only 2 steps:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Find all the locally linked dependencies of all the subprojects recursively.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Go into each linked dependency and run <code style="color:#d3009b">npm i</code> or <code style="color:#d313dd">yarn install</code>.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A bash wizard could probably do this in 3 lines. I won't put here all the code because it's too obvious to warrant a discussion, but you can have a <a href="https://github.com/streetwriters/notesnook/blob/master/scripts/bootstrap.mjs" target="_blank" class="c_accent">look at the code here</a>.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Conclusion</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is our story. Don't take it as a pitch against using Lerna or any other tool. Lerna serves many other usecases which I haven't (yet) found a need for.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In the end, use whatever works best for you and when that breaks look for the simplest solution instead of the "industry solution" or "trends". After all, the only thing a tool is meant to do is save your time.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.KrUK8ZQn.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Notesnook is on Privacy Guides]]></title>
            <link>https://blog.notesnook.com/notesnook-on-privacy-guides</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-on-privacy-guides</guid>
            <pubDate>Tue, 27 Jun 2023 12:01:22 GMT</pubDate>
            <description><![CDATA[Notesnook is now (finally & officially) recommended by the Privacy Guides — the de-facto resource for well-vetted & truly privacy friendly alternatives.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">After <a href="https://discuss.privacyguides.net/t/notesnook-evernote-alternative/174" target="_blank" class="c_accent">a few months of back and forth</a>, Notesnook is now (finally &amp; officially) <a href="https://www.privacyguides.org/en/notebooks/#notesnook" target="_blank" class="c_accent">recommended by the Privacy Guides</a> — the de-facto resource for well-vetted &amp; truly privacy friendly alternatives.</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/notesnook-on-privacy-guides.BkrzSfqT.webp 640w, https://blog.notesnook.com/assets/static/notesnook-on-privacy-guides.B5-4CYVp.webp 1024w, https://blog.notesnook.com/assets/static/notesnook-on-privacy-guides.KjPuiX86.webp 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/jpg" srcset="https://blog.notesnook.com/assets/static/notesnook-on-privacy-guides.ChbbsWo-.jpg 640w, https://blog.notesnook.com/assets/static/notesnook-on-privacy-guides.CAbWJnzc.jpg 1024w, https://blog.notesnook.com/assets/static/notesnook-on-privacy-guides.BKNUBDAW.jpg 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/notesnook-on-privacy-guides.ChbbsWo-.jpg" srcset="https://blog.notesnook.com/assets/static/notesnook-on-privacy-guides.ChbbsWo-.jpg 640w, https://blog.notesnook.com/assets/static/notesnook-on-privacy-guides.CAbWJnzc.jpg 1024w, https://blog.notesnook.com/assets/static/notesnook-on-privacy-guides.BKNUBDAW.jpg 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Notesnook on Privacy Guides" class="bdr_default w_full"></picture></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This marks a new milestone on our journey to become the best private note taking tool out there. We have already been <a href="http://privacytools.io/encrypted-notebooks" target="_blank" class="c_accent">listed on PrivacyTools.io</a> — another great resource for finding great tools that put your privacy first.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Privacy Guides</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Privacy Guides is an amazing place to find some real gems in the privacy tools space. That and their vibrant community makes Privacy Guides one of the best places to go if you are looking to protect your privacy online.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">What's next?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We'd love to be on every list of privacy respecting productivity tools but unfortunately, not everyone thinks the same. Let's see what the future holds.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you are a member/owner of such a list, I'd love to chat!</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.zHbY6Hiz.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[May 2023 in Review]]></title>
            <link>https://blog.notesnook.com/may-2023-in-review</link>
            <guid isPermaLink="false">https://blog.notesnook.com/may-2023-in-review</guid>
            <pubDate>Thu, 01 Jun 2023 12:01:22 GMT</pubDate>
            <description><![CDATA[Attachment previews, new attachment manager, Notesnook on F-Droid and more!]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">From the very dramatic political situation in Pakistan to occasional days long Internet outages, May 2023 can easily be termed as one of the most intense months ever. Through all this turmoil, we have been hard at work to ship some of the most highly requested features ever. Read on to find out what we accomplished in May 2023.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Attachment previews</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can now preview PDFs &amp; all kinds of images directly inside Notesnook. No need for long &amp; tedious process of downloading &amp; opening the attachments in a separate application. Here's a glimpse of how it looks:</p>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/attachment-previews.Dwt5RUcW.webp 640w, https://blog.notesnook.com/assets/static/attachment-previews.7qTR4xkV.webp 1024w, https://blog.notesnook.com/assets/static/attachment-previews.B-OHAoiY.webp 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/attachment-previews._mkTBRYa.png 640w, https://blog.notesnook.com/assets/static/attachment-previews.j5xz3qHW.png 1024w, https://blog.notesnook.com/assets/static/attachment-previews.BLG6A_pv.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/attachment-previews._mkTBRYa.png" srcset="https://blog.notesnook.com/assets/static/attachment-previews._mkTBRYa.png 640w, https://blog.notesnook.com/assets/static/attachment-previews.j5xz3qHW.png 1024w, https://blog.notesnook.com/assets/static/attachment-previews.BLG6A_pv.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Attachment previews" class="bdr_default w_full"></picture></figure>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Other types of attachments</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Our plan is to implement the most useful kind of preview for each attachment type. For example, videos &amp; audios are best previewed inlined. They don't need to be opened in a separate pane like PDFs. Similarly, other documents like text &amp; markdown files would work best in a separate pane.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Eventually, we will allow users to add support for different file types using extensions. It isn't practical or feasible to support every single format officially.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">New attachment manager</h2>
<figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/new-attachment-manager.e6DH1IHI.webp 640w, https://blog.notesnook.com/assets/static/new-attachment-manager.BYbe5DK9.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/jpeg" srcset="https://blog.notesnook.com/assets/static/new-attachment-manager.DHmVLZse.jpeg 640w, https://blog.notesnook.com/assets/static/new-attachment-manager.lGFX9KSK.jpeg 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/new-attachment-manager.DHmVLZse.jpeg" srcset="https://blog.notesnook.com/assets/static/new-attachment-manager.DHmVLZse.jpeg 640w, https://blog.notesnook.com/assets/static/new-attachment-manager.lGFX9KSK.jpeg 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="New attachment manager" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Designed more like a file manager.</figcaption></figure>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have redesigned the attachment manager to include quick filters for different types of files making it very easy to manage your attachments. We have also improved support for performing bulk actions like deleting, downloading &amp; rechecking the attachments.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>Note: Orphaned attachments are those attachments that don't belong to any note.</em></p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Web clipper on Chrome Web Store</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>Thanks to <strong>@Aaron from our Discord community</strong> for donating a Chrome Web Store account and making it possible for the Notesnook Web Clipper to be published on the Chrome Web Store.</em></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Ever since we release the Notesnook Web Clipper, this has been one of our biggest drawbacks. Our goal with Notesnook has always been to make it easier for people to use private &amp; safe software. Unfortunately, until today it was a huge hassle for anyone wanting to use our Chrome extension. No anymore!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I am really excited to announce that Notesnook Web Clipper is now officially available from the Chrome Web Store! Go check it out and don't forget to leave a review.</p>
<a href="https://chrome.google.com/webstore/detail/notesnook-web-clipper/kljhpemdlcnjohmfmkogahelkcidieaj/" target="_blank"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/chrome-web-store.C3k9D6gs.webp 340w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/chrome-web-store.CFbiOIvf.png 340w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/chrome-web-store.CFbiOIvf.png" srcset="https://blog.notesnook.com/assets/static/chrome-web-store.CFbiOIvf.png 340w" sizes="100vw" alt="Notesnook Web Clipper available on Chrome Web Store" style="max-width:340px" class="bdr_default w_full"></picture></figure></a>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Notesnook on F-Droid</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook was already available on F-Droid via the IzzyOnDroid repository (which I believe almost everyone includes, anyway). However, the important thing about getting approved on F-Droid is satisfying their requirements. No Google libraries whatsoever, no lock-in, reproducible builds, fully open source etc. This is why it's a huge thing for an app to get approved on F-Droid.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It took us several months of back and forth with the wonderful people over at F-Droid to finally get Notesnook approved on F-Droid. We faced issues such as failing builds due to missing libraries, ABI version problems, build minutes running out on GitLab and a few other issues. The result? You can now easily install Notesnook from F-droid without requiring usage of any 3rd party repositories.</p>
<a href="https://f-droid.org/en/packages/com.streetwriters.notesnook/" target="_blank"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/fdroid.BPoAnZfO.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/fdroid.DrQRoiuE.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/fdroid.DrQRoiuE.png" srcset="https://blog.notesnook.com/assets/static/fdroid.DrQRoiuE.png 640w" sizes="100vw" alt="Notesnook available on F-Droid" style="max-width:640px" class="bdr_default w_full"></picture></figure></a>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>Note: The F-droid version of Notesnook is slightly different than the one available on GitHub.</em></p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Notesnook Importer</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There were 2 very critical bugs that were fixed:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Images and attachments linked in Markdown notes were getting ignored.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Empty Google Keep notes with image attachments wouldn't import.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Both of these issues have been fixed in v1.7.7 of the Importer. Go <a href="https://importer.notesnook.com/" target="_blank" class="c_accent">try it here</a>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">An important thing to keep in mind is that the Importer doesn't distinguish between an attachment and an internal link so any link that resolves to a local file will get imported as an attachment. For example, <code style="color:#dd10ef">[this is an internal link](important-link.md)</code> will get imported as an attachment named <code style="color:#ea10ea">important-link.md</code>.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Community achievments</h2>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We <strong>crossed 6k stars</strong> on our GitHub repo! (<a href="https://github.com/streetwriters/notesnook" target="_blank" class="c_accent">Go star the repo</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We <strong>crossed 5k followers</strong> on our Twitter account. (<a href="https://twitter.com/notesnook" target="_blank" class="c_accent">Follow us</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Over <strong>400 students &amp; teachers have benefited</strong> from our Education plan. (<a href="https://notesnook.com/education" target="_blank" class="c_accent">Apply now</a>)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Over <strong>900 people visit</strong> our website every day (an increase from 700).</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Thank you for supporting us!</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">What the future holds</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In the next months, our focus will be on releasing the following features (no promises!):</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Backup &amp; restore attachments</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Default notebook/topic/tag</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Changing default note title</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Changing default date format</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Support for sharing files/images to Notesnook</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Custom themes</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Note linking</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Self hosting</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If the above sounds interesting to you, I recommend <a href="https://twitter.com/notesnook" target="_blank" class="c_accent">following us on Twitter</a> where we regularly post updates about the app.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/attachment-previews._mkTBRYa.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Telemetry in Notesnook: Opt-in vs Opt-out]]></title>
            <link>https://blog.notesnook.com/telemetry-opt-in-vs-opt-out</link>
            <guid isPermaLink="false">https://blog.notesnook.com/telemetry-opt-in-vs-opt-out</guid>
            <pubDate>Wed, 15 Feb 2023 12:01:22 GMT</pubDate>
            <description><![CDATA[To opt-in or not to opt-in. That is the question. Which is more ethical? Opt-in? Opt-out? Zero telemetry?]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Being in the FOSS space, you slowly come to realize that people absolutely abhor the idea of telemetry in an open source app. It irks them to know that the software they are using might be sending <em>some</em> data behind their backs. It doesn't matter what data its sending — just that it shouldn't do it.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">On the other hand, if a proprietary software is not held up to the same standards. It can get away with tracking its users, usually at the spyware level, without any complaints. The problem therefore is: should there be telemetry in FOSS?</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Surveys and Polls</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Anyone who has built a software knows how insignificant the results of a survey/poll can be. Only a minority of a minority of users would take the time to fill them out which quickly puts them in the useless category. Even if you are Elon Musk, with 122 million followers, polls are not a great way to get user opinion because the bigger the sample size, the bigger the gap between majority &amp; minority.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Which brings us to...</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Opt-out telemetry</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We see this practice in a lot of FOSS/proprietary software. There is usually some popup like "Help us improve XYZ by sending completely anonymous usage data." which is already filled out. The users who take the time to read carefully might uncheck that box but most people out there skip ahead and use the app.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">That's the problem.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The user hasn't actually consented to being tracked. They might not even know they are being tracked and they certainly don't know how to turn it off. Its like with the Cookie consent popups you see these days thanks to GDPR; they are so annoying that most of us just click allow to get rid of it because any other button will take you to this "Settings" page to check/uncheck a bunch of nonsense data categories, half of which can't even be unchecked.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The bright side of this story is that companies get <em>a lot of data</em> as a consequence and if its not FOSS, only Allah knows what data that actually is. So opt-out is clearly bad but what about opt-in?</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Opt-in telemetry</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">No one actually does this because its too good to be true and too useless to make any sense. Just kidding.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Opt-in telemetry is fully consent-based and I have rarely seen an implementation of it that makes me want to opt-in. A dead checkbox saying, "Help us improve XYZ" doesn't work because as mentioned above, users just move on.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Ethically speaking, there can be no better balance of collecting usage data. Realistically speaking, there's no worse option because, as is the case with surveys, the sample size is just too small to be actionable.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Wrong data is worse than no data.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Suppose only 1% of your users opt-in to telemetry (which is not unrealistic) and after some internal debate about removing some feature X, you decide to just add a telemetry endpoint to see how many users are actually using it. See where this is going?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Out of those 1% users about 0.9% users regularly use feature X. So far so good. You run to your team and tell them about your recent discovery only to be pointed out, "what about the other 99% users?". At this point usually companies either give up or make telemetry opt-out.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">What about zero telemetry?</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Zero telemetry</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is every privacy enthusiast's pipe dream. Unfortunately, its only being practically used in side projects who rarely anyone cares about. But putting that aside, what's the point of zero telemetry?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Zero telemetry means zero visibility into what's going on in the app. Who is using which feature? What percentage of users are annoyed by this popup? Which is exactly what privacy entails. We see this in VPNs under the "no logs" policy (not sure how true that is).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">So what should a company do?</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">What about Notesnook?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">What about it? We <a href="https://github.com/streetwriters/notesnook/releases/tag/v2.4.3" target="_blank" class="c_accent">recently went "opt-in"</a> from "opt-out" due to privacy reasons. In all honesty, I wanted to go the "zero telemetry" route because the data we collect has very little practical use (aside from encouragement). So why "opt-in" then?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I think this is a safe "middle ground" in the world of telemetry. It won't put us at a disadvantage when we do have important data we want to collect from users &amp; and it also won't allow us to be privacy invasive. The above points regarding "opt-in telemetry" hold true for Notesnook as well because for now we only have that dead checkbox. But that is going to change very soon (more on this in a later blog post).</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">What we collect?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I think it is very important for users to know what &amp; when data is being collected. Here's a (small) list of events that trigger telemetry:</p>
<div style="overflow-x:auto"><table><thead><tr><th>Event name</th><th>Event detail</th><th>Usage</th></tr></thead><tbody><tr><td>version</td><td>Sent on startup</td><td>This lets us know which app versions are in active use.</td></tr><tr><td>checkout:started</td><td>Sent when you start checkout on the web/desktop app.</td><td>Useful for conversion tracking.</td></tr><tr><td>offer:claimed</td><td>Sent when you start the checkout with a coupon code applied. This includes which coupon code you used.</td><td>Useful to track which promos have better conversion.</td></tr><tr><td>announcement:dismissed</td><td>Sent when you dismiss an in-app announcement.</td><td>This lets us know the conversion rate for the announcements we show &amp; consequently helps us figure out which types of announcements to avoid.</td></tr><tr><td>purchase:initiated</td><td>Sent whenever the Notesnook Pro dialog (the one with the list of all the features) is shown to you. This can be voluntary or via accessing a premium feature.</td><td>This is especially useful to see the rate of conversion &amp; also helps us see if we are showing this dialog too much.</td></tr><tr><td>link:social</td><td>Sent whenever you open Notesnook social media link from the email verified screen.</td><td>This is pointless and will be removed</td></tr><tr><td>announcement:cta</td><td>Sent whenever you an announcement CTA is invoked.</td><td>Useful to track rate of conversion similar to <code style="color:#ea028d">announcement:dismissed</code></td></tr><tr><td>/account/created</td><td>Sent when you create an account.</td><td>This lets us know the number of accounts being created on each platform.</td></tr></tbody></table></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You might be wondering what else gets sent with all of that. We use Umami analytics for telemetry but instead of their tracker script we have an in-house script to control exactly the amount of data to collect. The main data collection function looks something like this:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">collect</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">event</span><span class="token operator">:</span> <span class="token maybe-class-name">PageView</span> <span class="token operator">|</span> <span class="token maybe-class-name">Event</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token keyword">const</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="3"></span>    <span class="token literal-property property">screen</span><span class="token operator">:</span> <span class="token punctuation">{</span> width<span class="token punctuation">,</span> height <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="4"></span>    <span class="token literal-property property">navigator</span><span class="token operator">:</span> <span class="token punctuation">{</span> language <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="5"></span>    <span class="token literal-property property">location</span><span class="token operator">:</span> <span class="token punctuation">{</span> hostname<span class="token punctuation">,</span> pathname<span class="token punctuation">,</span> search <span class="token punctuation">}</span>
<span class="ln-num" data-num="6"></span>  <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token dom variable">window</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>
<span class="ln-num" data-num="8"></span>  <span class="token keyword">const</span> screen <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">x</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>height<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span>  <span class="token keyword">const</span> currentUrl <span class="token operator">=</span>
<span class="ln-num" data-num="10"></span>    <span class="token punctuation">(</span>event<span class="token punctuation">.</span><span class="token property-access">type</span> <span class="token operator">===</span> <span class="token string">'pageview'</span> <span class="token operator">&amp;&amp;</span> event<span class="token punctuation">.</span><span class="token property-access">url</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>pathname<span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>search<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span>
<span class="ln-num" data-num="12"></span>  <span class="token keyword">const</span> body <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="13"></span>    <span class="token literal-property property">payload</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="14"></span>      <span class="token literal-property property">website</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">the-website-id</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
<span class="ln-num" data-num="15"></span>      hostname<span class="token punctuation">,</span>
<span class="ln-num" data-num="16"></span>      screen<span class="token punctuation">,</span>
<span class="ln-num" data-num="17"></span>      language<span class="token punctuation">,</span>
<span class="ln-num" data-num="18"></span>      <span class="token literal-property property">url</span><span class="token operator">:</span> currentUrl<span class="token punctuation">,</span>
<span class="ln-num" data-num="19"></span>      <span class="token spread operator">...</span>event
<span class="ln-num" data-num="20"></span>    <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="21"></span>    <span class="token literal-property property">type</span><span class="token operator">:</span> event<span class="token punctuation">.</span><span class="token property-access">type</span>
<span class="ln-num" data-num="22"></span>  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="23"></span>
<span class="ln-num" data-num="24"></span>  <span class="token keyword control-flow">try</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="25"></span>    <span class="token keyword control-flow">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>baseUrl<span class="token punctuation">,</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="26"></span>      <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">'POST'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="27"></span>      <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="28"></span>        <span class="token string-property property">'Content-Type'</span><span class="token operator">:</span> <span class="token string">'application/json'</span>
<span class="ln-num" data-num="29"></span>      <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="30"></span>      <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>body<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="31"></span>      <span class="token literal-property property">keepalive</span><span class="token operator">:</span> <span class="token boolean">true</span>
<span class="ln-num" data-num="32"></span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="33"></span>  <span class="token punctuation">}</span> <span class="token keyword control-flow">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="34"></span>    <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">error</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="35"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="36"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can read more about Umami at <a href="https://umami.is/" target="_blank" class="c_accent">their website</a>.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Conclusion</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There's no conclusion, unfortunately. It's all a trade-off between privacy &amp; convenience.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.BuZxJD8b.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Introducing Notesnook for Education]]></title>
            <link>https://blog.notesnook.com/introducing-notesnook-for-education</link>
            <guid isPermaLink="false">https://blog.notesnook.com/introducing-notesnook-for-education</guid>
            <pubDate>Tue, 01 Nov 2022 12:01:22 GMT</pubDate>
            <description><![CDATA[A new step towards making Notesnook accessible and private for everyone. Privacy is not just for the priviledged few — it's for everyone.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">1st of November, the start of Winter and I am super excited to announce that we are launching Notesnook for Education plan that'll allow all students, teachers &amp; faculty members to enjoy Notesnook Pro at an affordable price of <strong>just 9.99 USD/yr</strong>. If you are a student or a faculty member, just fill out <a href="https://notesnook.com/education" target="_blank" class="c_accent">this form</a> to claim the discount.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">What Notesnook offers to students</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook poses itself as a complete note taking solution &amp; tries to stay out of your where while you do your work. I know that sounds like gibberish but give it a try. We have done our very best to make the UX as smooth as possible:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">There are no new concepts to learn — it's just note taking.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">There are multiple ways to organize your notes.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We offer a single no-nonsense editor that'll fit your every need.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">To top that all off you can share your notes using Monographs to anyone in the world (and even protect them with a password).</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">You can attach 100% encrypted attachments by default so all your PDFs, slides, videos &amp; documents are safe.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Since Notesnook works on every device with 100% feature parity, you don't have to worry about always carrying a laptop with you. Just pull up the Notesnook app on your phone and start editing.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Notesnook also offers built-in exports in PDF, HTML &amp; Markdown (other formats are in the works).</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With that said, there are some limitations that we are working on:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">There is no drawing/handwriting support.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Things like PDFs &amp; documents needed to be downloaded before viewing.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Attachments cannot be directly shared.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If any of the above will affect your workflow, I recommend waiting until we sort these out before fully migrating. You can <a href="https://notesnook.com/roadmap" target="_blank" class="c_accent">keep an eye on our roadmap</a> &amp; <a href="https://twitter.com/notesnook" target="_blank" class="c_accent">follow us on Twitter</a> to know when these features land.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Why not free?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I am aware how some products out there give out a completely free option to educators &amp; students and while we aspire towards that it is not realistic for a startup at our scale. We are just starting out and need all the help we can get — this includes subscriptions, documentation, support etc. But we didn't want to use that as an excuse which is why we are offering Notesnook Pro to educators &amp; students at our lowest possible price i.e. 9.99 USD/yr.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Closing note</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With this new plan I am hoping more students &amp; teachers will be encouraged to adopt private alternatives. Privacy is a global issue &amp; each of us has to fight against the Information Mafia that wants to spy on us and use our data for their own nefarious purposes. Any step in that direction is a step in the right direction.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.DeRL-7Gy.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[It's time to leave Bitwarden]]></title>
            <link>https://blog.notesnook.com/its-time-to-leave-bitwarden</link>
            <guid isPermaLink="false">https://blog.notesnook.com/its-time-to-leave-bitwarden</guid>
            <pubDate>Tue, 06 Sep 2022 12:01:22 GMT</pubDate>
            <description><![CDATA[Bitwarden recently raised a $100M seed fund. What does this mean for you as a user? Is Bitwarden going to become another example of what happens when innocent looking startups get seed funded?]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In March 2022 I received a very well-intentioned message from MYKI Security, the company behind the MYKI password manager. Underneath all the corporate speech their plight was simple: it is very hard to turn a profit for our company so we are deciding to move to other venues.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you haven't used or heard of MYKI, here's what it offered in <a href="https://web.archive.org/web/20220302211259/https://myki.com/app/" target="_blank" class="c_accent">its free tier</a>:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Unlimited passwords</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Unlimited devices</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Unlimited 2FA keys</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Unlimited secure notes</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Unlimited credit cards</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A very appealing offer for most users. Their intended customers were teams and businesses but turn out there are not that many of them. Most teams either don't want a password manager ("not a problem" scenario) or they are already using a different one (LastPass etc.).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The problem with MYKI was simple: they didn't want to charge the majority of their users to encourage higher signups, more growth, etc. I was also one of their free users who refused to upgrade because I never felt any need to. I could store unlimited passwords &amp; unlimited 2FA keys, and have them synced across unlimited devices. Why would I upgrade when there were 0 extra features I needed? That was also one of the reasons it was so hard to migrate when they did eventually kill off MYKI — no other password manager, including Bitwarden, offered the breadth of features MYKI did in their free tier.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Note:</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you are a privacy-minded individual who also takes notes, Notesnook recently <a href="https://github.com/streetwriters/notesnook" target="_blank" class="c_accent">open sourced all their clients</a> with <a href="https://notesnook.com/roadmap" target="_blank" class="c_accent">plans</a> to open source a self-hostable sync server soon.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I'd appreciate that a lot if you can help us by starring the GitHub repo and/or <a href="https://notesnook.com/" target="_blank" class="c_accent">trying out Notesnook</a>.</p>
</blockquote>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">What does this have to do with Bitwarden?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Let me explain.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Until now the only source of revenue behind Bitwarden were their users (or teams/businesses). Unlike MYKI they do not include 2FA in the free tier but <em>everything</em> else was free (very appealing for new users). What <em>did</em> they charge though? $10/yr:</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/bitwarden_pricing.Cpq-eDq4.webp 640w, https://blog.notesnook.com/assets/static/bitwarden_pricing.DpgzRv9v.webp 1024w, https://blog.notesnook.com/assets/static/bitwarden_pricing.Vrh0p2FY.webp 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/bitwarden_pricing.CJ2HjPXM.png 640w, https://blog.notesnook.com/assets/static/bitwarden_pricing.BDb7j5AL.png 1024w, https://blog.notesnook.com/assets/static/bitwarden_pricing.C-9X5OLO.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/bitwarden_pricing.CJ2HjPXM.png" srcset="https://blog.notesnook.com/assets/static/bitwarden_pricing.CJ2HjPXM.png 640w, https://blog.notesnook.com/assets/static/bitwarden_pricing.BDb7j5AL.png 1024w, https://blog.notesnook.com/assets/static/bitwarden_pricing.C-9X5OLO.png 1600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Bitwarden pricing" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">That is not a boatload of money. Why? Because they know that no one would pay otherwise. Password managers don't have a lot of innovation left in them and the only saving grace behind Bitwarden is its open-source nature. Password managers are a fire-and-forget kind of software. You never even open a password manager except to unlock it. The only real use case of a password manager is to, well, manage your passwords. That's it.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Bitwarden is the only password manager recommended by the majority of the security community to layman users due to its simplicity &amp; security. A wonderful product, to be sure.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But the problem with their approach would be apparent to any business-minded person: there is not a lot of selling potential in a password manager. After a point, you are forced to look towards the B2B market (via MYKI Teams or Bitwarden Business plans) but that is clearly <em>not</em> enough.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The evidence lies in the recent move by <a href="https://bitwarden.com/blog/accelerating-value-for-bitwarden-users-bitwarden-raises-usd100-million/" target="_blank" class="c_accent">Bitwarden to raise a $100M seed fund</a> to shift their focus towards other opportunities (authentication). Bitwarden succeeded. It got popular and hit its peak potential (aside from acquiring more users).</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">What does the $100M seed funding mean for Bitwarden?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Any kind of seed funding is focused on 2 primary things:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The potential in the team/product to succeed.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The need to generate a lot of revenue.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">VC-backed startups are intensely revenue-focused. They no longer have the option to say no to business propositions that'd be harmful to the freeloaders at the cost of paywall-ing content, cutting features from the free plan, etc. This makes perfect business sense. You can't make money by selling free food.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Why would Bitwarden be any different? $100M is a lot of money. The first thing that'll happen is either the team's focus will move towards a lot of different things <em>or</em> they'll build things around Bitwarden turning it into a complicated mess no one wants to use (unlikely).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As with any VC-backed startup, Bitwarden will have a limited time to turn a significant profit by whatever means possible. Investors don't like to lose money. And let me tell you one thing. $100M is a lot of money and once you see &amp; handle that kind of money, it's hard to look back. I wouldn't be surprised if after the first few years of trying, Bitwarden the password manager gets discontinued.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The impact of this decision will reverberate over a few months. If the core team starts a different project, you'll see reduced activity in their GitHub repository — the death of Bitwarden is near.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">But Bitwarden is open source!</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It won't become another MYKI, for sure (unless they close source their code (highly unlikely)) but won't it impact you, as a user, when suddenly the perfect product becomes buggy due to negligence? I am not sure what'll happen here. Maybe the community will take up the project, maybe another company will acquire Bitwarden. In any case, Bitwarden's future is quite shaky.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can always stay on a certain version, never upgrade, self host and live off of that but how feasible is that for the average Joe? The open source nature of Bitwarden makes this sort of thing more bearable but it'll still be a huge hit.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">What can you do?</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When MYKI got acquired moving my passwords was extremely hard. There are not many open source (or closed source) alternatives that offer the same set of features as Bitwarden. I eventually settled with Keepass which isn't ideal either (manual sync, old UI, broken clients) but I am quite sure it isn't going anywhere (it's community-run, multiple clients, no centralization, no servers, just a DB version that can't be killed off).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Does this recent news mean Bitwarden is dying off? No. All this is conjecture but based on a lot of previous evidence. Seed funding is extremely bad for the consumer market (unless you are Netflix). Should you migrate to another password manager? Too soon to say anything. You're probably safe for a few years. It is only wise to prepare beforehand, however, in case something of this kind does happen. This includes researching &amp; trying out alternatives or,if you are like me, building your own.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Closing thoughts</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I honestly hope this doesn't happen and that Bitwarden stays true to their original purpose but it appears highly unlikely. Moves like these should make you cautious of what can happen (exactly what this blog aims to do).</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Privacy</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.BNypHXNd.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Notesnook is going open source!]]></title>
            <link>https://blog.notesnook.com/notesnook-is-going-open-source</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-is-going-open-source</guid>
            <pubDate>Tue, 09 Aug 2022 12:01:22 GMT</pubDate>
            <description><![CDATA[I am so excited to open source Notesnook this month! This blog will give you a clear idea about why we are open sourcing, what we aim to accomplish & how you can help.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I am so excited to be open sourcing Notesnook this month! This blog will give you a clear idea about why we are doing that, what we aim to accomplish &amp; how you can help. If you have any questions, feel free to <a href="https://twitter.com/notesnook/" target="_blank" class="c_accent">reach out on Twitter</a>.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">A matter of trust</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When I look at the private note-taking apps space, there's only one name: Standard Notes. Its trustworthiness is undeniable because it is open source regardless of how good or bad it is. On top of that, every respectable privacy-oriented software is also open source. A clear indication that we are the odd one out.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">So why now? The main reason for the delay was inexperience &amp; lack of resources. Notesnook has been a 3-man project for a year &amp; we could do only so much. Maintaining an open source project requires a lot of energy. Energy we could not spare.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Along with that, we had very little idea about how important open sourcing is. Over time and feedback from our users, we realized the opportunity we were missing. Many people reached out to us concerned about their privacy. More often than not, the conversation went something like this:</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Hi, I love what you guys are doing with Notesnook, but I cannot migrate all my notes because it is not open source. Have you guys thought about open sourcing yet?</p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Users' feedback has been the primary motive behind Notesnook's growth (the new editor, fixing performance issues, making sync reliable, etc.). It was clear after a time that a lot of users were hesitating to join Notesnook because we weren't open source.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And the community's response speaks for itself after we broke the news last month. We received 100+ stars on <a href="https://github.com/streetwriters/notesnook/" target="_blank" class="c_accent">our GitHub repo</a>, 700+ followers on <a href="https://twitter.com/notesnook/" target="_blank" class="c_accent">Twitter</a>, and a lot of people came back after hearing that we are going open source. All these are validation enough that this is a good idea.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Community</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Open source is nothing without its community of contributors, developers &amp; everyone else working together to make something great. We'd be crazy to not let Notesnook benefit from that.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/contributors.CqVjTQh7.webp 388w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/contributors.B4l5myoc.png 388w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/contributors.B4l5myoc.png" srcset="https://blog.notesnook.com/assets/static/contributors.B4l5myoc.png 388w" sizes="100vw" alt="List of contributors on Notesnook" style="max-width:388px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Would love to see 1K+ contributors here</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And to be honest, Notesnook would not be here today without the efforts of the open source community; starting from the lowest layer (Linux) to the highest (React). We owe this contribution to OSS.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">(A)GPLv3</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For this very reason, we have decided to go with an (A)GPLv3 license. I wanted a less permissive license for Notesnook (BSL or something) but <a href="https://drewdevault.com/2021/01/20/FOSS-is-to-surrender-your-monopoly.html" target="_blank" class="c_accent">this wonderful article by Drew DeVault</a> changed my mind. Freedom is a part of open source &amp; we do not want to restrict you in any way.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">DCO vs. CLA</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I had no idea what <a href="https://elinux.org/Developer_Certificate_Of_Origin" target="_blank" class="c_accent">Developer Certificate Of Origin</a> was until I heard about <a href="https://drewdevault.com/2021/01/19/Elasticsearch-does-not-belong-to-Elastic.html" target="_blank" class="c_accent">what happened with Redis Labs &amp; ElasticSearch</a>. I have signed a few CLAs myself (React Native) not knowing what they implied.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Re-licensing our contributors' hard work under a proprietary license would break all levels of trust. Even if that is legal, I cannot allow Notesnook to allow that. Everyone deserves to be acknowledged for their contributions which is why we are going with DCO instead. I am hopeful that this will create a safe environment for everyone &amp; encourage more contributions.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you have a minute, I'd appreciate if you can <a href="https://github.com/streetwriters/notesnook/" target="_blank" class="c_accent">star our GitHub repo</a>.</p>
</blockquote>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Transparency</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Building Notesnook behind closed doors gives us the safety to be lazy &amp; make mistakes. An easy thing to do since no one can point them out. I know for a fact that there are some obvious flaws in Notesnook invisible to my eyes. No matter what I do I cannot completely eliminate that. Open source gets around this by being transparent. What is transparency?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The fact that anyone can scrutinize my code &amp; point out the mistakes. At first this is anxiety-inducing but from a different perspective it is also vital for Notesnook to be bulletproof. 1 person cannot fix the bugs a 100 people can.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Independence</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I was a user of the MYKI password manager for the last couple of years. This year when <a href="https://en.wikipedia.org/wiki/Myki_(password_manager)" target="_blank" class="c_accent">they got acquired</a> I was forced to migrate — not something I enjoy. I liked how MYKI made managing passwords so simple but they shut down their servers and I could do nothing.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If MYKI had been open source this whole scenario would have been so much different. I'd have self-hosted it and gone on with my life.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">That's what I want Notesnook to be. A piece of software that'll live on even if we decide to shut it down. Independent from decisions of a single party.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">What will be open sourced?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Here's a list of all the different components of Notesnook that'll be open-sourced:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Web, desktop &amp; mobile clients</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Shared core</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Editor &amp; extensions</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Sync server</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Most of these will be in a monorepo for seamless integration. The sync server get its own repo, though.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">How can you contribute?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In the first few weeks, we'll be focusing on improving repository's health. This includes:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Writing lots &amp; lots of tests</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Improving documentation</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Refactoring the client apps to share as much logic as possible</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We won't be accepting any feature pull requests until the repository is in a good enough health. Still, you can help out in various ways:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Bug fixes (&amp; bug reports)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Writing documentation</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Taking part in refactoring discussions</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Writing tests (if you are familiar with Playwright, Jest, etc.)</li>
</ol>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Ending thoughts</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">All in all, I am super excited about making Notesnook open source. If it were possible I'd open source it immediately but I want to do it <em>right</em> to avoid any regrets later. This is by no means a sole mission and <a href="https://github.com/streetwriters/notesnook/discussions/747" target="_blank" class="c_accent">you can take part in the conversation</a> without any hesitation on GitHub.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.CkFaOOvc.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Why Notesnook Requires an Email Address]]></title>
            <link>https://blog.notesnook.com/why-notesnook-requires-an-email-address</link>
            <guid isPermaLink="false">https://blog.notesnook.com/why-notesnook-requires-an-email-address</guid>
            <pubDate>Sat, 30 Jul 2022 12:01:22 GMT</pubDate>
            <description><![CDATA[Email is a necessary part of our digital lives & learning to share it safely is essential for our online privacy. Learn why Notesnook continues to require an email address during sign up instead of a more anonymous approach.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">At first glance it makes no sense for a privacy respecting service to ask for personal information as important as an email. Notesnook is not indifferent to this critique and continues to stick to this approach for a couple of reasons.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Why not email?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Before we can get to why email still makes sense we need to first discuss how email can be abused to put things in perspective.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">1. User identification</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you only ever sign up for a single account in the entirety of your digital lifespan, you'd be safe. However, that's not the case for most users. As long as you use a single email address to sign up for multiple services (doesn't matter how separate they are), your email address can become a point of identification.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is the primary danger to your privacy.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">User identification can be used to build a profile on you which can then be sold or used for ad targeting.</p>
</blockquote>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">2. Spam</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Every year a lot of people fall for email spam which primarily happens due to data leaks by services either intentionally or accidentally. If you never give someone your email nobody can reach out to you &amp; hence you remain safe.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A lot of spammers these days mine user data using different identifiers including email address &amp; phone number to build a profile. This profile is then used to compose a very authentic looking spam email which anyone can fall for.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Anecdote</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A few months ago we received an email from Apple requesting more information to process our monthly payments. The email listed everything including our company name, phone number, full name etc. and hence looked extremely authentic. Even the email address domain was apple.com.</p>
</blockquote>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">So why email then?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The above 2 points should be enough for anyone to stop signing up using their email. However, it is important to get a clear picture of why all the services including Notesnook depend on email.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">1. Preventing account spam &amp; bot accounts</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As a service provider blocking bot accounts &amp; preventing abuse is necessary to keep everything running smoothly. Email address offers one way to do that without worsening the UX too much.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Email signups require an additional verification step during account creation which discourages bulk account creation.</p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is in no way bulletproof but requiring an email can significantly slow the attacker down. As opposed to a username which can easily be randomized &amp; automated.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">2. Memorable &amp; accessible</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Email has become like a phone number. (In fact, phone number &amp; email pose very similar risks to privacy due to their widespread adoption.) Everyone with Internet access has an email account which makes it tremendously accessible.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">People still forget their email but generally speaking it is much harder to remember a different username for each service. This is exactly why a lot of users stick to a single username. However, usernames can easily be hijacked.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Since each email address is inherently unique it's not a trivial task to hijack it without hijacking the whole email account first. This makes email addresses irreplaceable &amp; easy to use.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">3. Account recovery</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The first thing users forget after signing up is their password. At this moment no private notes app except Notesnook offers account recovery which is very frustrating for a lot of users. The fault obviously lies on the user side: they should use a password manager or remember the password well but this is already a losing argument. As a service it is <em>our</em> job to provide rescue when things go wrong, not the user's.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The fact that privacy always comes at some cost to convenience is the very reason most people hesitate when it comes to protecting their privacy. In short, account recovery is mandatory regardless of whether a service is E2EE or not.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook offers 3 ways to recover access to your account:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Using a recovery key</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Using a backup</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Resetting everything &amp; starting from scratch</li>
</ol>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The primary way account recovery works is:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">You enter your email address</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The service provider sends you an email with a link or a code</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">You click on the link in the email to start the account recovery process</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Without an email address it becomes impossible to verify the ownership of an account.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">4. Reachable</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Server breaches are real. Data gets compromised or some other critical issue can happen. Having a way to reach out to our users in a reliable way is vital to ensuring their safety and email is <em>the</em> way of communicating with our users.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Aside from email, Notesnook also has an in-app announcement system for notifying users about critical issues &amp; ways to avoid them. However, not everyone always has Notesnook open in which case they can miss an important announcement.</p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In addition to this, some services including Notesnook use email to regularly update about the product's progress. All these non-critical emails are optional, of course but they provide a really great way to keep the user in the loop with minimal effort on the user's side.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Sharing your email safely</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The first instinct after reading this might be to ditch email entirely. It's an old technology full of holes patched over &amp; over again. Try as we might, however, we have to live with email.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Since almost all services have a way to sign up via email, it's necessary to know how to anonymize yourself without depending on any single service. It's inevitable that at least one service out there will be careless with your personal data. Trusting the vendor to do all the heavy-lifting will leave your security full of holes.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">1. Anonymize your email</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It's cool to have a recognizable email address (e.g. <code style="color:#d3138d">&lt;lastname&gt;.&lt;firstname&gt;@&lt;domain&gt;</code>) but it's also the first thing connecting the email address to you. The first step should always be to randomize your email address in such a way that it cannot be used to know who's behind it just by reading it.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Instead of using your full name or initials in your email, you can adopt a fake persona and use that.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Doing the above can be quite inconvenient if your email is already connected everywhere. For this I recommend slow migration.</p>
</blockquote>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">2. Email aliases</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Email aliases cloak your real email offering complete anonymity while sharing the same email address. A sender sends an email using the alias but it arrives in your real inbox. Simple &amp; convenient.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">An email alias acts as a proxy.</p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Since you can create unlimited aliases for a single email address, this can be an easy way to dissociate one account from another.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can use any of these services to set up email aliasing:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://simplelogin.io/" target="_blank" class="c_accent">SimpleLogin</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://anonaddy.com/" target="_blank" class="c_accent">AnonDaddy</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://relay.firefox.com/" target="_blank" class="c_accent">Firefox Relay</a></li>
</ol>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Email aliasing can also help you narrow down potential culprits in case you start receiving spam emails. This obviously requires you to use a unique email alias for each service or each group of services which might not always be convenient.</p>
</blockquote>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">3. Disposable emails</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I generally don't recommend using disposable emails (unless you are creating a disposable account as well) due a variety of reasons. However, they can be a good way to sign up without exposing your real email address.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Do note that disposable emails are transitory i.e., you can lose access to an email address at the worst possible moment and there's no way to get it back. This makes disposable email a risky way to ensure anonymity.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">The Future of Notesnook &amp; Email</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">At this point in time (<code style="color:#e0dc06">July 30, 2022 PKT UTC+5</code>) if Notesnook's database is compromised the only data at risk is users' email addresses. While we recommend each user to take the necessary steps to anonymize their emails (as listed above), it is also mandatory as a private note taking app to improve our security.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">These are some of the practical steps Notesnook is going to take to prevent potential leakage of users' email addresses.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">1. Ephemeral email address storage by default</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Most uses of email listed above (account recovery, spam prevention etc.) do not require storing the email at all. However, it is essential to verify that the entered email address is the right one. For this we can store a one-way hashed version of an email address (instead of plaintext) for the purpose of verification.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Whenever the user starts a process that requires email verification, the client app can ask the user for their email. This is already being done for some processes (e.g. account recovery, account creation, log in etc.).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">All this, however, requires trust since the emails will still be transmitted as plain text and it'll be up to us to decide whether to store them or not.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">2. Opt-in email address persistence</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Storing emails ephemerally offers greater security but it prevents us from reaching out to the user in case of a critical issue. Currently, this is not a problem because all email addresses are stored in the database as plain text &amp; can be easily retrieved.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To solve this problem, we will give the user the choice of whether they want their email stored for external communication or not. This would allow users who have already anonymized their email address to continue to benefit.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We can further increase the security by encrypting the stored emails using an asymmetric key. The public part of the key can be stored on the server whereas the private part can be isolated to a different machine.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I haven't thought too much on this but it's possible to do this in a safe way.</p>
</blockquote>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">The result</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">After all these measures have been taken, if Notesnook's database is breached there'd be no useful data for the attacker at all:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">All user generated data will be encrypted (notes, notebooks etc.)</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">User's personal information will either not exist or will also be encrypted (email address)</li>
</ol>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Conclusion</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Email is a necessary part of our digital lives &amp; learning to share it safely is essential for our online privacy. Employing techniques such as email aliasing we can easily anonymize our online presence because we cannot always depend on the service provider to protect our privacy.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Privacy</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.D1YcYNrl.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Is DuckDuckGo Search & Browser Really Private?]]></title>
            <link>https://blog.notesnook.com/is-duckduckgo-search-browser-really-private</link>
            <guid isPermaLink="false">https://blog.notesnook.com/is-duckduckgo-search-browser-really-private</guid>
            <pubDate>Fri, 27 May 2022 12:01:22 GMT</pubDate>
            <description><![CDATA[I don't trust Microsoft and consequentially I can't trust DuckDuckGo. Microsoft holds all the cards when it comes to DuckDuckGo which is a ticking bomb. In short, can DuckDuckGo exist without Bing? No.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">On May 23, 2022, <a href="https://twitter.com/thezedwards/status/1528808759027331072" target="_blank" class="c_accent">Zach Edwards tweeted</a> about the apparent hole in DuckDuckGo browsers which permitted Microsoft trackers to function unhindered. This had nothing to do with DuckDuckGo search and to be fair the controversy around this matter was blown way out of context. DuckDuckGo's CEO &amp; Founder, Gabriel Weinberg, <a href="https://twitter.com/yegg/status/1528838114558484480" target="_blank" class="c_accent">very</a> <a href="https://news.ycombinator.com/item?id=31490603" target="_blank" class="c_accent">passionately</a> <a href="https://news.ycombinator.com/item?id=31491441" target="_blank" class="c_accent">shattered</a> all the false claims around the misunderstood tweet. But privacy is all about trust and if a privacy-focused company is found to be deliberately compromising on users' privacy in one of their products, the implications eventually spread out.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For the confused, due to a contract with Bing, DuckDuckGo <a href="https://twitter.com/yegg/status/1528838579455250434" target="_blank" class="c_accent">cannot block 3rd-party Microsoft trackers on 3rd-party websites</a> in the DuckDuckGo browser. For example, DuckDuckGo browser won't block Microsoft trackers on the New York Times website (if it exists there).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Is this bad? Yes and here's why.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Privacy is about trust</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Even though DuckDuckGo browser &amp; search are very different, the company &amp; people behind them are the same. What does it mean for a company to be hand-tied in one of their products when it comes to privacy? It implies a severe weakness in all of their products: their dependence on Microsoft.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The question then changes to is it possible that DuckDuckGo will be forced (via a contract by Microsoft) to allow Microsoft tracking in their search? And suddenly it all starts looking worse and worse.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/ddg-trackers.lf3IgiUQ.webp 482w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/ddg-trackers.Blsj3I8k.png 482w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/ddg-trackers.Blsj3I8k.png" srcset="https://blog.notesnook.com/assets/static/ddg-trackers.Blsj3I8k.png 482w" sizes="100vw" alt="Brave reports 10+ trackers on DuckDuckGo search" style="max-width:482px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Brave reports 10+ trackers on DuckDuckGo search</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It's not important that DuckDuckGo doesn't do this currently. Even the fact that it doesn't block Microsoft trackers on 3rd party websites is unimportant. What's important is the reason <em>why</em> they do what they do &amp; <em>why</em> they can't stop. Which brings us to...</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Privacy is about independence</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When DuckDuckGo launched, it was hyped as the privacy-focused alternative to Google &amp; Bing. A lot of people migrated to DuckDuckGo &amp; a lot of people still use it but DuckDuckGo wasn't the first (or the last) search engine to offer privacy focused interface to Bing or Google. <a href="https://startpage.com" target="_blank" class="c_accent">StartPage</a>, founded in 2006, used Google as their underlying search engine similar to how DuckDuckGo used Bing.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Is a search engine still private if it depends on one of the most privacy invasive search engines around? Perhaps. But launching a competing service that heavily relies on a rival for their core functionality is a time bomb.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><a href="https://help.duckduckgo.com/duckduckgo-help-pages/results/sources/" target="_blank" class="c_accent">All this isn't done in secret</a> i.e., there are legal contracts between companies (Google &amp; StartPage; Microsoft &amp; DuckDuckGo) which allows DuckDuckGo to use Bing's search index. This puts a huge shadow on these privacy focused search engines. They cannot grow independently and while promoting privacy &amp; freedom, these very services are bound by legal contracts to act otherwise.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There is no privacy or freedom if you are not independent:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Can DuckDuckGo exist without Bing? No.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Does Bing decide what to show me when I search on DuckDuckGo? In a way, yes.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Even though DuckDuckGo provides a filter between me &amp; Bing, how impenetrable actually is that filter?</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It doesn't matter if I am not directly being <a href="https://news.ycombinator.com/item?id=31491815" target="_blank" class="c_accent">"coerced"</a> by Bing. As long as I use DuckDuckGo it's essentially the same thing because Microsoft can (and does) coerce DuckDuckGo. Which brings us to...</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Privacy is about financial freedom</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">DuckDuckGo's main source of revenue is <a href="https://help.duckduckgo.com/duckduckgo-help-pages/company/ads-by-microsoft-on-duckduckgo-private-search/" target="_blank" class="c_accent">search ads (like Google) which are Bing search ads</a>. They cannot be profitable if they cut ties with Bing but it also means that Bing, and therefore Microsoft, controls their revenue stream and their business.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In their own words:</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Microsoft and DuckDuckGo have partnered to provide a search solution that delivers relevant advertisements to you while protecting your privacy. If you click on a Microsoft-provided ad, you will be redirected to the advertiser’s landing page through Microsoft Advertising’s platform. <strong>At that point, Microsoft Advertising will use your full IP address and user-agent string so that it can properly process the ad click and charge the advertiser.</strong>
— <a href="https://help.duckduckgo.com/duckduckgo-help-pages/company/ads-by-microsoft-on-duckduckgo-private-search/" target="_blank" class="c_accent">Ads by Microsoft on DuckDuckGo Private Search</a></p>
</blockquote>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/ddg-ad.DX6x87Q1.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/ddg-ad.DbiQXjqR.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/ddg-ad.DbiQXjqR.png" srcset="https://blog.notesnook.com/assets/static/ddg-ad.DbiQXjqR.png 640w" sizes="100vw" alt="Bing search ad on DuckDuckGo" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Bing search ad on DuckDuckGo</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This means that DuckDuckGo cannot make decisions that directly (or negatively) impact Bing or Microsoft. In other words, DuckDuckGo exists as long as Microsoft allows it to exist.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Conclusion</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The fact of the matter is, Microsoft holds almost all the cards here and that's bad news if you care about privacy. This new controversy only makes DuckDuckGo's position weaker, no matter how passionately Gabriel goes about defending DuckDuckGo.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I don't trust Microsoft and consequentially I can't trust DuckDuckGo. I migrated to <a href="https://search.brave.com" target="_blank" class="c_accent">Brave Search</a> when they launched their Beta and haven't looked back since. I love that they are <a href="https://brave.com/brave-search-beta/" target="_blank" class="c_accent">independent both in terms of revenue &amp; functionality</a>.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Privacy</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.cPvAP6BO.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Using Javascript to Render Invalid HTML]]></title>
            <link>https://blog.notesnook.com/using-javascript-to-render-invalid-html</link>
            <guid isPermaLink="false">https://blog.notesnook.com/using-javascript-to-render-invalid-html</guid>
            <pubDate>Tue, 24 May 2022 12:01:22 GMT</pubDate>
            <description><![CDATA[HTML is an extremely flexible markup language but that doesn't mean there are no rules. Over the course of many years, browser engines became quite resilient to malformed HTML. But they weren't ready for Javascript.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">HTML is an extremely flexible markup language. No really, you can put <code style="color:#9d0fc4">&lt;div&gt;</code>s inside <code style="color:#c61190">&lt;span&gt;</code>s inside <code style="color:#ed097f">&lt;p&gt;</code> tags but that doesn't mean there are no rules. Over the course of many years, browser engines became quite resilient to malformed HTML.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But they weren't ready for Javascript.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">The Curious Case of a <code style="color:#e004a5">&lt;pre&gt;</code> tag</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I was working on the Notesnook Web Clipper and testing it out on various different websites when I came across this anomaly on a <a href="https://hackernoon.com/how-to-take-screenshots-in-the-browser-using-javascript-l92k3xq7" target="_blank" class="c_accent">Hackernoon blog post</a>.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-html"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>paragraph<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>bla bla bla
<span class="ln-num" data-num="2"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>pre</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>code</span><span class="token punctuation">&gt;</span></span>code<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>code</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>pre</span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="3"></span>bla bla bla
<span class="ln-num" data-num="4"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span></code></pre></div>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>You can inspect the actual source by yourself. Anywhere, you see an <code style="color:#c60975">inline code</code>, be sure that it's actually a <code style="color:#e004a5">&lt;pre&gt;</code> tag.</em></p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There's nothing really wrong with this code syntactically. But browsers can't render it as it is, because ideally, <a href="https://stackoverflow.com/a/5371841" target="_blank" class="c_accent"><code style="color:#e004a5">&lt;pre&gt;</code> tags should not be rendered inside <code style="color:#ed097f">&lt;p&gt;</code> tags</a>. So what do browsers do? They move the <code style="color:#e004a5">&lt;pre&gt;</code> tag <em>outside</em> loyal to the HTML spec.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-html"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>paragraph<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>bla bla bla<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="2"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>pre</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>code</span><span class="token punctuation">&gt;</span></span>code<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>code</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>pre</span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="3"></span>bla bla bla
<span class="ln-num" data-num="4"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But that doesn't explain how Hackernoon guys were forcing the browser to render the invalid code.</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Aside:</strong>
Whoever runs the blog over at Hackernoon, kudos! But seriously, why not just use <code style="color:#9e0044">&lt;code&gt;</code> for inline code instead of <code style="color:#e004a5">&lt;pre&gt;</code> by forcibly <code style="color:#ad13d3">display: inline</code> it using CSS? I am genuinely curious.</p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Turns out the answer was quite simple: <strong>NextJS</strong> or more precisely: <strong>Javascript</strong>.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">NextJS Hydration</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">How NextJS works is that it sends over a raw, static HTML blob which gets rendered by the browser. After the first render, this blob is <em>hydrated</em> and turned into a dynamic monster of a website. This allows you to have all the SEO benefits + a modern web experience. A cool strategy if the result before &amp; after the hydration step looks visually the same.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In the case of Hackernoon, it doesn't.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To confirm my suspicion about Javascript being responsible for this, I manually throttled the network speed and stopped the hydration step. And lo and behold! The browser was working as it should i.e., it was automatically fixing the bad code as expected. So for sure the magic was somewhere in the client-side Javascript.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I opened the DevTools, switched to the console tab, and just for the heck of it tried putting a <code style="color:#e004a5">&lt;pre&gt;</code> tag into a <code style="color:#ed097f">&lt;p&gt;</code> tag. It worked.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-js"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> paragraph <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">createElement</span><span class="token punctuation">(</span><span class="token string">'p'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span><span class="token keyword">const</span> pre <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">createElement</span><span class="token punctuation">(</span><span class="token string">'pre'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>paragraph<span class="token punctuation">.</span><span class="token method function property-access">appendChild</span><span class="token punctuation">(</span>pre<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And then I realized, since NextJS is based on React which renders everything via Javascript where this restriction doesn't exist; this was happening automatically. I bet the folks over at Hackernoon don't even realize what's going on.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Does this work for all tags?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Next thing, I tried putting a <code style="color:#ed097f">&lt;p&gt;</code> tag inside <code style="color:#ea09b9">&lt;br&gt;</code>. It failed of course but I received no errors. This was, however, expected because <code style="color:#ea09b9">&lt;br&gt;</code> tag doesn't have children.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But what about putting <code style="color:#e004a5">&lt;pre&gt;</code> inside <code style="color:#dd0dbb">&lt;script&gt;</code>?</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-js"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> script <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">createElement</span><span class="token punctuation">(</span><span class="token string">'script'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span><span class="token keyword">const</span> pre <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">createElement</span><span class="token punctuation">(</span><span class="token string">'pre'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>script<span class="token punctuation">.</span><span class="token method function property-access">appendChild</span><span class="token punctuation">(</span>pre<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span><span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span>script<span class="token punctuation">.</span><span class="token property-access">outerHTML</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span><span class="token comment">// Result: '\x3Cscript&gt;&lt;pre&gt;&lt;/pre&gt;\x3C/script&gt;'</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><em>(Strange how the <code style="color:#ea04ea">&lt;</code> characters are printed as <code style="color:#c60997">\x3C</code>).</em></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There's no way to render HTML inside <code style="color:#dd0dbb">&lt;script&gt;</code> tag directly, right? This shouldn't work &amp; it doesn't because everything inside <code style="color:#dd0dbb">&lt;script&gt;</code> is supposed to be Javascript, right?!</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-html"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="2"></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
<span class="ln-num" data-num="3"></span>    <span class="token operator">&lt;</span>pre<span class="token operator">&gt;</span>hello<span class="token operator">&lt;</span><span class="token operator">/</span>pre<span class="token operator">&gt;</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span>  </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="5"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">&gt;</span></span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But what if you append a <code style="color:#dd0dbb">&lt;script&gt;</code> that contains a <code style="color:#e004a5">&lt;pre&gt;</code> inside it via Javascript? You get this.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/pre-inside-script.vW4SWoxH.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/pre-inside-script.BWpT9Nrg.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/pre-inside-script.BWpT9Nrg.png" srcset="https://blog.notesnook.com/assets/static/pre-inside-script.BWpT9Nrg.png 640w" sizes="100vw" alt="<html><head></head><body><script><pre>var test = &quot;hi&quot;;</pre></script></body></html>" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">You can put pre tags inside a script tag via Javascript.</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This means you can write Javascript inside a <code style="color:#e004a5">&lt;pre&gt;</code> tag and put it inside a <code style="color:#dd0dbb">&lt;script&gt;</code> tag...and it will get treated like a normal <code style="color:#e004a5">&lt;pre&gt;</code> tag by the browser. Crazy.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">So what?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Okay yes, this isn't ground breaking but it <em>is</em> annoying. I can live with it but it raises a few questions:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Why not add checks to prevent this?</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">What else might be living around in browsers that can be similarly twisted &amp; turned?</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Is this intended? And if so, why prevent it on the inital render?</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Maybe someone can reasonably answer those but one thing is for sure: Javascript adds a baffling amount of uncertainty &amp; unpredictability to the web.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.D2ofg-qq.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Improving User Account Security with Two-factor Authentication]]></title>
            <link>https://blog.notesnook.com/improving-user-account-security-with-2fa</link>
            <guid isPermaLink="false">https://blog.notesnook.com/improving-user-account-security-with-2fa</guid>
            <pubDate>Fri, 25 Mar 2022 12:01:22 GMT</pubDate>
            <description><![CDATA[Our 1st priority was always to make our users' notes secure, and 2FA was a huge step towards that. Learn how we added 2-factor authentication & why we made certain decisions.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When we were building Notesnook, our first and foremost priority was to ensure the security &amp; privacy of users' Notes. With that in mind, we added end-to-end encryption, app lock, notes vault &amp; encrypted attachments. All these kept your notes safe but the security of your account was at constant risk.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Anyone with your password could easily login &amp; read your notes — a troubling thought, I know. To remedy this, we have now added 2FA. This blog post is about how we went about adding 2-factor authentication &amp; why we made certain decisions along the way.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Choosing 2FA providers</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you have setup 2FA in other apps, you know how varying the different 2FA methods can be. Some services offer 2FA via SMS, others via an authentication (TOTP) app, or both.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In Notesnook, we went with 3 main options:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Authenticator app like <a href="https://getaegis.app/" target="_blank" class="c_accent">Aegis</a> or <a href="https://github.com/raivo-otp/ios-application" target="_blank" class="c_accent">Raivo</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">SMS</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Email</li>
</ol>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/2fa-methods.CpcPgkDz.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/2fa-methods.B1aeA4oc.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/2fa-methods.B1aeA4oc.png" srcset="https://blog.notesnook.com/assets/static/2fa-methods.B1aeA4oc.png 640w" sizes="100vw" alt="2FA methods in Notesnook" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">2FA methods in Notesnook</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The security experts will, no doubt, balk at the thought of <code style="color:#e8108e">2FA via email</code> but let me explain. The more secure a method, the more cumbersome it becomes. For example, to setup 2FA via an authenticator app you have to go through multiple steps before you are done. There's no doubt about the level of security but it's also necessarily complex — not everyone's piece of cake</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Obviously, the authenticator app is still the recommended method.</p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">On the other hand, email is super easy. Almost everyone already has it and setting up 2FA takes less than a minute. This encourages even the laziest crowd to secure their accounts via 2FA.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Implementing 2FA</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The most time consuming thing was fine tuning the UX behind 2FA. I researched the setup/auth flows of a lot of different services (Google, GitHub etc) and took the best thing from each.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I found GitHub's 2FA setup flow to be the most straight-forward with minimum amount of steps. Google's was most complex with a lot of back and forth. Discord's flow, while easy, was confusing.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In the end, I settled with a 3-step process:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Selecting a method</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Verifying via the selected method</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Backing up the recovery codes</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Done</li>
</ol>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/2fa-steps.DQ38rk8e.webp 640w, https://blog.notesnook.com/assets/static/2fa-steps.D82he-SF.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/jpeg" srcset="https://blog.notesnook.com/assets/static/2fa-steps.DnudzG-t.jpeg 640w, https://blog.notesnook.com/assets/static/2fa-steps.BmLHG8w8.jpeg 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/2fa-steps.DnudzG-t.jpeg" srcset="https://blog.notesnook.com/assets/static/2fa-steps.DnudzG-t.jpeg 640w, https://blog.notesnook.com/assets/static/2fa-steps.BmLHG8w8.jpeg 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="2FA setup steps in Notesnook" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">2FA setup steps in Notesnook</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">No navigation, no fancy animations, no back and forth — just a dialog that lets you enable 2FA in 3 steps.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">The drama of SMS providers</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The worst thing about being in Pakistan is that a lot of services simply refuse to offer their businesses here. I wanted to go with Twilio but they refused to accept Debit cards. After some more research, I found a couple of other providers:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://vonage.com/" target="_blank" class="c_accent">Vonage</a> — failed with an error at sign up &amp; login. Support was no help.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://plivo.com/" target="_blank" class="c_accent">Plivo</a> — doesn't accept IPs from Pakistan</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://bandwidth.com/" target="_blank" class="c_accent">Bandwidth</a> — only offers SMS services to US &amp; Canada</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://messagebird.com/" target="_blank" class="c_accent">MessageBird</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://telnyx.com/" target="_blank" class="c_accent">Telnyx</a></li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I wasted a couple of hours trying out the first 2. I really liked Plivo's API that allowed sending custom 2FA codes at no extra cost. Unfortunately, they don't allow Pakistan.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Telnyx was a good option but I found <a href="https://developers.telnyx.com/docs/v2/verify/quickstart" target="_blank" class="c_accent">their documentation a little confusing</a>. In the end, I went with MessageBird. Aside from <em>very</em> competitive pricing, they have an absolutely gorgeous UI (and very easy to use dashboard).</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/messagebird-dashboard.CBeDZTfS.webp 640w, https://blog.notesnook.com/assets/static/messagebird-dashboard.Cg-eCdn0.webp 1024w, https://blog.notesnook.com/assets/static/messagebird-dashboard.hB93WhYW.webp 1600w, https://blog.notesnook.com/assets/static/messagebird-dashboard.BUc3Ntvq.webp 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/messagebird-dashboard.Dzqhgylc.png 640w, https://blog.notesnook.com/assets/static/messagebird-dashboard.BUiuMbl4.png 1024w, https://blog.notesnook.com/assets/static/messagebird-dashboard.v1FGoIb8.png 1600w, https://blog.notesnook.com/assets/static/messagebird-dashboard.DYZOS-jz.png 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/messagebird-dashboard.Dzqhgylc.png" srcset="https://blog.notesnook.com/assets/static/messagebird-dashboard.Dzqhgylc.png 640w, https://blog.notesnook.com/assets/static/messagebird-dashboard.BUiuMbl4.png 1024w, https://blog.notesnook.com/assets/static/messagebird-dashboard.v1FGoIb8.png 1600w, https://blog.notesnook.com/assets/static/messagebird-dashboard.DYZOS-jz.png 1920w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="MessageBird dashboard" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">MessageBird dashboard — isn't it just gorgeous?</figcaption></figure></div>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">MessageBird</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The server-side logic took me about 10 minutes before I found out that MessageBird doesn't allow sending custom 2FA codes. Oops. Contacting their support was no help (although they responded very quickly).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I had to change the implementation details to allow MessageBird to verify phone numbers with their own codes but that's about it.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">So far it has been working really good. Perfect choice.</p>
<h5 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">The E.164 standard format</h5>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">MessageBird only accepts phone numbers in <a href="https://en.wikipedia.org/wiki/E.164" target="_blank" class="c_accent">E.164 standard format</a>. What's E.164 standard? I have no idea (I didn't bother to read the document) but I got the basics of it after checking a few libraries.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Phone numbers have various formats:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-txt"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>+1 (292)-2292-291
<span class="ln-num" data-num="2"></span>(292)-2292-291
<span class="ln-num" data-num="3"></span>.
<span class="ln-num" data-num="4"></span>.</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And this gets really hard to parse so there needs to be a standard — the E.164 standard. This turns a complex combination of symbols &amp; digits into:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-txt"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>+12922292291</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I am sure there's more to it. Obviously, I didn't go about doing this myself; I found <a href="https://github.com/aftership/phone" target="_blank" class="c_accent">this amazing library</a> by <a href="https://www.aftership.com/" target="_blank" class="c_accent">AfterShip</a> to do all the heavy lifting. Be sure to star their repo.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">OAuth2 + MFA = Hard work</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">OAuth2 is very unclear about 2FA leaving it up to the developers to figure out — a risky thing for sure. I checked the Auth0 documentation and essentially stole their implementation idea.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A normal authentication flow works like this (very simplified):</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-txt"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>Client -&gt; user credentials -&gt; auth server -&gt; access token generation -&gt; client</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With the addition of 2FA, the above becomes:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-txt"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>Step 1: Client -&gt; user credentials -&gt; auth server -&gt; 2FA detected -&gt; 2FA token generation -&gt; client
<span class="ln-num" data-num="2"></span>Step 2: Client -&gt; 2FA code + user credentials -&gt; auth server -&gt; 2FA verification -&gt; access token generation -&gt; client</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The difference between a 2FA token &amp; an <code style="color:#0f0f0f">access_token</code> is permissions. 2FA token only allows touching certain endpoints so nope, you won't be able to get users' notes with just the 2FA token.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The reason this is required in the first place is to prevent spam by making the 2FA endpoints public.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">The End</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">All in all, it took us a week to finalize 2FA cross-platform and shipped in <code style="color:#f215b7">v1.8.3</code>. <a href="https://notesnook.com/downloads" target="_blank" class="c_accent">Download Notesnook</a> on all your devices to try it out!</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.1UzwxQ4W.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Using React Native Skia to Build a 60 FPS Free-hand Drawing App]]></title>
            <link>https://blog.notesnook.com/drawing-app-with-react-native-skia</link>
            <guid isPermaLink="false">https://blog.notesnook.com/drawing-app-with-react-native-skia</guid>
            <pubDate>Tue, 22 Mar 2022 12:01:22 GMT</pubDate>
            <description><![CDATA[Let's explore @spotify/react-native-skia by building a fully native, 60 FPS, free-hand drawing app that allows us to change stroke color, width & export the drawing as an SVG file.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When it comes to comparing Flutter with React Native, the most popular argument has been a more native user experience with buttery smooth animations and graphics drawing support. Flutter uses <a href="https://skia.org/" target="_blank" class="c_accent">Skia</a>, a very popular, open-source 2D graphics engine made by Google and used in Chrome and Android to draw everything natively with great performance.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">On the other hand, React Native uses the platform specific rendering engines which is great but much slower. All this slowness is there because of a bridge which sends all information to Java/Objective-C in batches from the Javascript side. Since everything is single threaded, under heavy load the FPS drops; you see blank spaces in <code style="color:#911007">FlatList</code>s, lag during scroll, and a lot of hang ups.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/jsi-image.BOnK09o0.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/jsi-image.B2CZr4Xa.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/jsi-image.B2CZr4Xa.png" srcset="https://blog.notesnook.com/assets/static/jsi-image.B2CZr4Xa.png 640w" sizes="100vw" alt="Communication between Javascript and Android/iOS environment with JSI" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Communication between Javascript and Android/iOS environment with JSI</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">React Native had a good increase in performance when <a href="https://hermesengine.dev/" target="_blank" class="c_accent">Hermes</a> came out but soon the React Native team realized that the only factor holding back performance was the JS-Native bridge. React Native needed a better and faster way of communication with native side. So the React Native team started to work on <a href="https://blog.notesnook.com/getting-started-react-native-jsi/" target="_blank" class="c_accent">the new architecture (JSI + Fabric + Turbo Modules)</a>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Today the new architecture is in it's final stages and about to be released to the public in the next React Native release changing many things for React Native. However, it's still very uncertain (due to lack of proper documentation) how the new architecture actually works.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Currently only a handful of libraries have used this new architecture. The most prominent ones have been <a href="https://github.com/software-mansion/react-native-reanimated" target="_blank" class="c_accent">react-native-reanimated</a> and <a href="https://github.com/Shopify/react-native-skia" target="_blank" class="c_accent">@shopify/react-native-skia</a>. Today we will be using <code style="color:#e20674">@shopify/react-native-skia</code> to build a fully native, 60 FPS, free-hand drawing app to explore the possibilities.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We will be building a very simple free-hand drawing app in React Native with the following features:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Free hand drawing</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Color selection</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Variable stroke</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Undo/redo</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Export the drawing to standard SVG format</li>
</ol>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Getting started</h2>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Create a new react native project</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are starting from a fresh react native project.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-bash"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>npx react-native init skia-drawing-app</code></pre></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Installing dependencies</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><code style="color:#dd0d6b">react-native-skia</code> is not yet released on npm hence we will add it directly from GitHub releases</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-bash"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token function">npm</span> <span class="token function">install</span> https://github.com/Shopify/react-native-skia/releases/download/v0.1.103-alpha/shopify-react-native-skia-0.1.103.tgz</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And <code style="color:#bb04cc">zustand</code> for React state management.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-bash"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token function">npm</span> <span class="token function">install</span> zustand</code></pre></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Project structure</h2>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-txt"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>.
<span class="ln-num" data-num="2"></span>┃  📦src
<span class="ln-num" data-num="3"></span>┃  ┣ 📂components
<span class="ln-num" data-num="4"></span>┃  ┃ ┣ 📜color.tsx
<span class="ln-num" data-num="5"></span>┃  ┃ ┣ 📜header.tsx
<span class="ln-num" data-num="6"></span>┃  ┃ ┣ 📜stroke.tsx
<span class="ln-num" data-num="7"></span>┃  ┃ ┗ 📜toolbar.tsx
<span class="ln-num" data-num="8"></span>┃  ┣ 📂drawing
<span class="ln-num" data-num="9"></span>┃  ┃ ┣ 📜constants.tsx
<span class="ln-num" data-num="10"></span>┃  ┃ ┣ 📜history.tsx
<span class="ln-num" data-num="11"></span>┃  ┃ ┣ 📜index.tsx
<span class="ln-num" data-num="12"></span>┃  ┃ ┗ 📜utils.tsx
<span class="ln-num" data-num="13"></span>┃  ┗ 📂store
<span class="ln-num" data-num="14"></span>┃  ┃ ┗ 📜index.ts
<span class="ln-num" data-num="15"></span>┣ 📜App.tsx</code></pre></div>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The <code style="color:#ce027c">components</code> folder has the toolbar &amp; all the basic components.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The <code style="color:#c10764">drawing</code> folder has all the logic + the drawing board.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We are using <code style="color:#bb04cc">zustand</code> for global state management. The store folder has our global state where we keep drawing paths and paint information.</li>
</ol>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Building the drawing board</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The drawing board consists of 3 components: <code style="color:#9b0330">Header</code>, <code style="color:#aa0b19">Canvas</code> and <code style="color:#a30841">Toolbar</code> interconnected by the global state as shown below.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/drawing_board.C49evK8c.webp 400w, https://blog.notesnook.com/assets/static/drawing_board.AtjQV4L8.webp 600w, https://blog.notesnook.com/assets/static/drawing_board.tYojZhuh.webp 800w, https://blog.notesnook.com/assets/static/drawing_board.Ed8Pmh95.webp 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/drawing_board.DUqTksO6.png 400w, https://blog.notesnook.com/assets/static/drawing_board.2MFtbc4d.png 600w, https://blog.notesnook.com/assets/static/drawing_board.BAnia0ic.png 800w, https://blog.notesnook.com/assets/static/drawing_board.COZ8jM7G.png 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/drawing_board.DUqTksO6.png" srcset="https://blog.notesnook.com/assets/static/drawing_board.DUqTksO6.png 400w, https://blog.notesnook.com/assets/static/drawing_board.2MFtbc4d.png 600w, https://blog.notesnook.com/assets/static/drawing_board.BAnia0ic.png 800w, https://blog.notesnook.com/assets/static/drawing_board.COZ8jM7G.png 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Native bindings not found error" class="bdr_default w_full"></picture></figure></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">1. The global state</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are using <code style="color:#bb04cc">zustand</code> for global state. The following interface will help you understand the purpose of each property in the state:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">import</span> <span class="token punctuation">{</span> DrawingInfo<span class="token punctuation">,</span> IPaint<span class="token punctuation">,</span> IPath <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@shopify/react-native-skia'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span><span class="token keyword">interface</span> <span class="token class-name">DrawingStore</span> <span class="token keyword">extends</span> <span class="token class-name">State</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="4"></span>  <span class="token doc-comment comment">/**
<span class="ln-num" data-num="5"></span>   * Array of completed paths for redrawing on the `Canvas`
<span class="ln-num" data-num="6"></span>   */</span>
<span class="ln-num" data-num="7"></span>  completedPaths<span class="token operator">:</span> CurrentPath<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span>  <span class="token doc-comment comment">/**
<span class="ln-num" data-num="9"></span>   * A function to update completed paths
<span class="ln-num" data-num="10"></span>   */</span>
<span class="ln-num" data-num="11"></span>  <span class="token function-variable function">setCompletedPaths</span><span class="token operator">:</span> <span class="token punctuation">(</span>completedPaths<span class="token operator">:</span> CurrentPath<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="12"></span>  <span class="token doc-comment comment">/**
<span class="ln-num" data-num="13"></span>   * Current stroke. Basically a paint object from Skia
<span class="ln-num" data-num="14"></span>   */</span>
<span class="ln-num" data-num="15"></span>  stroke<span class="token operator">:</span> IPaint<span class="token punctuation">;</span>
<span class="ln-num" data-num="16"></span>  <span class="token doc-comment comment">/**
<span class="ln-num" data-num="17"></span>   * Width of the stroke, used when creating stroke.
<span class="ln-num" data-num="18"></span>   */</span>
<span class="ln-num" data-num="19"></span>  strokeWidth<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="20"></span>  <span class="token doc-comment comment">/**
<span class="ln-num" data-num="21"></span>   * Color of the stroke
<span class="ln-num" data-num="22"></span>   */</span>
<span class="ln-num" data-num="23"></span>  color<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="24"></span>  <span class="token function-variable function">setStrokeWidth</span><span class="token operator">:</span> <span class="token punctuation">(</span>strokeWidth<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="25"></span>  <span class="token function-variable function">setColor</span><span class="token operator">:</span> <span class="token punctuation">(</span>color<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="26"></span>  <span class="token function-variable function">setStroke</span><span class="token operator">:</span> <span class="token punctuation">(</span>stroke<span class="token operator">:</span> IPaint<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="27"></span>
<span class="ln-num" data-num="28"></span>  <span class="token doc-comment comment">/**
<span class="ln-num" data-num="29"></span>  Width &amp; height information of the canvas used for svg export 
<span class="ln-num" data-num="30"></span>  */</span>
<span class="ln-num" data-num="31"></span>  canvasInfo<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>DrawingInfo<span class="token operator">&gt;</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="32"></span>  <span class="token function-variable function">setCanvasInfo</span><span class="token operator">:</span> <span class="token punctuation">(</span>canvasInfo<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>DrawingInfo<span class="token operator">&gt;</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="33"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The state stores and coordinates everything between all the different components.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">2. Canvas</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Canvas is where all the drawing takes place (obviously).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Objective:</strong>
Draw a path when the user touches the screen &amp; moves their finger, and save it.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-tsx"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">import</span> <span class="token imports"><span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token maybe-class-name">Canvas</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span>  <span class="token maybe-class-name">ExtendedTouchInfo</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="4"></span>  <span class="token maybe-class-name">ICanvas</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="5"></span>  <span class="token maybe-class-name">Path</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="6"></span>  <span class="token maybe-class-name">Skia</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="7"></span>  <span class="token maybe-class-name">SkiaView</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="8"></span>  <span class="token maybe-class-name">TouchInfo</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="9"></span>  useDrawCallback<span class="token punctuation">,</span>
<span class="ln-num" data-num="10"></span>  useTouchHandler
<span class="ln-num" data-num="11"></span><span class="token punctuation">}</span></span> <span class="token keyword">from</span> <span class="token string">'@shopify/react-native-skia'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="12"></span><span class="token keyword">import</span> <span class="token imports"><span class="token maybe-class-name">React</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> useCallback<span class="token punctuation">,</span> useRef<span class="token punctuation">,</span> useState <span class="token punctuation">}</span></span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span><span class="token keyword">import</span> <span class="token imports"><span class="token punctuation">{</span>
<span class="ln-num" data-num="14"></span>  <span class="token maybe-class-name">LayoutChangeEvent</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="15"></span>  <span class="token maybe-class-name">SafeAreaView</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="16"></span>  useWindowDimensions<span class="token punctuation">,</span>
<span class="ln-num" data-num="17"></span>  <span class="token maybe-class-name">View</span>
<span class="ln-num" data-num="18"></span><span class="token punctuation">}</span></span> <span class="token keyword">from</span> <span class="token string">'react-native'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="19"></span><span class="token keyword">import</span> <span class="token imports">useDrawingStore<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token maybe-class-name">CurrentPath</span> <span class="token punctuation">}</span></span> <span class="token keyword">from</span> <span class="token string">'../store'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="20"></span><span class="token keyword">import</span> <span class="token imports"><span class="token maybe-class-name">Header</span></span> <span class="token keyword">from</span> <span class="token string">'../components/header'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="21"></span><span class="token keyword">import</span> <span class="token imports">history</span> <span class="token keyword">from</span> <span class="token string">'./history'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="22"></span><span class="token keyword">import</span> <span class="token imports"><span class="token maybe-class-name">Toolbar</span></span> <span class="token keyword">from</span> <span class="token string">'../components/toolbar'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="23"></span>
<span class="ln-num" data-num="24"></span><span class="token keyword">const</span> <span class="token function-variable function">Drawing</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="25"></span>  <span class="token comment">// Is user touching the screen</span>
<span class="ln-num" data-num="26"></span>  <span class="token keyword">const</span> touchState <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="27"></span>  <span class="token comment">// Instance of canvas for imperative access</span>
<span class="ln-num" data-num="28"></span>  <span class="token keyword">const</span> canvas <span class="token operator">=</span> <span class="token generic-function"><span class="token function">useRef</span><span class="token generic class-name"><span class="token operator">&lt;</span>ICanvas<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="29"></span>  <span class="token comment">// The current path which the user is drawing. The value is reset when finger is raised from screen</span>
<span class="ln-num" data-num="30"></span>  <span class="token keyword">const</span> currentPath <span class="token operator">=</span> <span class="token generic-function"><span class="token function">useRef</span><span class="token generic class-name"><span class="token operator">&lt;</span>CurrentPath <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="31"></span>  <span class="token keyword">const</span> <span class="token punctuation">{</span> width <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useWindowDimensions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="32"></span>  <span class="token comment">// Array of completed paths from global state</span>
<span class="ln-num" data-num="33"></span>  <span class="token keyword">const</span> completedPaths <span class="token operator">=</span> <span class="token function">useDrawingStore</span><span class="token punctuation">(</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> state<span class="token punctuation">.</span><span class="token property-access">completedPaths</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="34"></span>  <span class="token comment">// A function in global state to add/remove paths</span>
<span class="ln-num" data-num="35"></span>  <span class="token keyword">const</span> setCompletedPaths <span class="token operator">=</span> <span class="token function">useDrawingStore</span><span class="token punctuation">(</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> state<span class="token punctuation">.</span><span class="token property-access">setCompletedPaths</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="36"></span>  <span class="token comment">// Stroke value from global state</span>
<span class="ln-num" data-num="37"></span>  <span class="token keyword">const</span> stroke <span class="token operator">=</span> <span class="token function">useDrawingStore</span><span class="token punctuation">(</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> state<span class="token punctuation">.</span><span class="token property-access">stroke</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="38"></span>  <span class="token comment">// Height of canvas, set on layout of View wrapping Canvas</span>
<span class="ln-num" data-num="39"></span>  <span class="token keyword">const</span> <span class="token punctuation">[</span>canvasHeight<span class="token punctuation">,</span> setCanvasHeight<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">400</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="40"></span>
<span class="ln-num" data-num="41"></span>  <span class="token keyword">const</span> onDraw <span class="token operator">=</span> <span class="token function">useDrawCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span>_canvas<span class="token punctuation">,</span> info<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="42"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>canvas<span class="token punctuation">.</span><span class="token property-access">current</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="43"></span>      useDrawingStore<span class="token punctuation">.</span><span class="token method function property-access">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">setCanvasInfo</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="44"></span>        width<span class="token operator">:</span> info<span class="token punctuation">.</span><span class="token property-access">width</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="45"></span>        height<span class="token operator">:</span> info<span class="token punctuation">.</span><span class="token property-access">height</span>
<span class="ln-num" data-num="46"></span>      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="47"></span>
<span class="ln-num" data-num="48"></span>      canvas<span class="token punctuation">.</span><span class="token property-access">current</span> <span class="token operator">=</span> _canvas<span class="token punctuation">;</span>
<span class="ln-num" data-num="49"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="50"></span>  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="51"></span>
<span class="ln-num" data-num="52"></span>  <span class="token comment">// We need to provide absolute height to the canvas as percentages/flex won't work.</span>
<span class="ln-num" data-num="53"></span>  <span class="token comment">// Therefore when the `View` renders, we get the height and keep it in state. This</span>
<span class="ln-num" data-num="54"></span>  <span class="token comment">// height will be the height of Canvas &amp; SkiaView components.</span>
<span class="ln-num" data-num="55"></span>  <span class="token keyword">const</span> <span class="token function-variable function">onLayout</span> <span class="token operator">=</span> <span class="token punctuation">(</span>event<span class="token operator">:</span> <span class="token maybe-class-name">LayoutChangeEvent</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="56"></span>    <span class="token function">setCanvasHeight</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span><span class="token property-access">nativeEvent</span><span class="token punctuation">.</span><span class="token property-access">layout</span><span class="token punctuation">.</span><span class="token property-access">height</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="57"></span>  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="58"></span>
<span class="ln-num" data-num="59"></span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="60"></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">SafeAreaView</span></span>
<span class="ln-num" data-num="61"></span>      <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="62"></span>        flex<span class="token operator">:</span> <span class="token number">1</span>
<span class="ln-num" data-num="63"></span>      <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="64"></span>    <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="65"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">View</span></span>
<span class="ln-num" data-num="66"></span>        <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="67"></span>          backgroundColor<span class="token operator">:</span> <span class="token string">'#f0f0f0'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="68"></span>          flex<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="69"></span>          alignItems<span class="token operator">:</span> <span class="token string">'center'</span>
<span class="ln-num" data-num="70"></span>        <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="71"></span>      <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="72"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Header</span></span> <span class="token punctuation">/&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="73"></span>
<span class="ln-num" data-num="74"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">View</span></span>
<span class="ln-num" data-num="75"></span>          <span class="token attr-name">onLayout</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onLayout<span class="token punctuation">}</span></span>
<span class="ln-num" data-num="76"></span>          <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="77"></span>            width<span class="token operator">:</span> width <span class="token operator">-</span> <span class="token number">24</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="78"></span>            flexGrow<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="79"></span>            backgroundColor<span class="token operator">:</span> <span class="token string">'#ffffff'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="80"></span>            borderRadius<span class="token operator">:</span> <span class="token number">10</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="81"></span>            overflow<span class="token operator">:</span> <span class="token string">'hidden'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="82"></span>            elevation<span class="token operator">:</span> <span class="token number">1</span>
<span class="ln-num" data-num="83"></span>          <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="84"></span>        <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="85"></span>          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">SkiaView</span></span>
<span class="ln-num" data-num="86"></span>            <span class="token attr-name">onDraw</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onDraw<span class="token punctuation">}</span></span>
<span class="ln-num" data-num="87"></span>            <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> height<span class="token operator">:</span> canvasHeight<span class="token punctuation">,</span> width<span class="token operator">:</span> width <span class="token operator">-</span> <span class="token number">24</span><span class="token punctuation">,</span> zIndex<span class="token operator">:</span> <span class="token number">10</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="88"></span>          <span class="token punctuation">/&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="89"></span>
<span class="ln-num" data-num="90"></span>          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Canvas</span></span>
<span class="ln-num" data-num="91"></span>            <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="92"></span>              height<span class="token operator">:</span> canvasHeight<span class="token punctuation">,</span>
<span class="ln-num" data-num="93"></span>              width<span class="token operator">:</span> width <span class="token operator">-</span> <span class="token number">24</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="94"></span>              position<span class="token operator">:</span> <span class="token string">'absolute'</span>
<span class="ln-num" data-num="95"></span>            <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="96"></span>          <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="97"></span>            </span><span class="token punctuation">{</span>completedPaths<span class="token operator">?.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="98"></span>              <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Path</span></span>
<span class="ln-num" data-num="99"></span>                <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>path<span class="token punctuation">.</span><span class="token property-access">path</span><span class="token punctuation">.</span><span class="token method function property-access">toSVGString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="100"></span>                <span class="token attr-name">path</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>path<span class="token punctuation">.</span><span class="token property-access">path</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="101"></span>                <span class="token attr-name">paint</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> current<span class="token operator">:</span> path<span class="token punctuation">.</span><span class="token property-access">paint</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="102"></span>              <span class="token punctuation">/&gt;</span></span>
<span class="ln-num" data-num="103"></span>            <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
<span class="ln-num" data-num="104"></span>          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Canvas</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="105"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">View</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="106"></span>
<span class="ln-num" data-num="107"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Toolbar</span></span> <span class="token punctuation">/&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="108"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">View</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="109"></span>    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">SafeAreaView</span></span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="110"></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="111"></span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="112"></span>
<span class="ln-num" data-num="113"></span><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token maybe-class-name">Drawing</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The <code style="color:#8c0b0f">Drawing</code> component is responsible for drawing paths on touch input. It consists of three Skia components.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#b51052">SkiaView</code>: Draws the current when user touches the screen and moves their finger.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#aa0b19">Canvas</code>: When the user raises their finger from the <code style="color:#b51052">SkiaView</code> that path is rendered on the underlying <code style="color:#aa0b19">Canvas</code>. This is why the <code style="color:#aa0b19">Canvas</code> component is rendered absolutely below the <code style="color:#b51052">SkiaView</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#a00704">Path</code>: Once a path is drawn on <code style="color:#b51052">SkiaView</code>, it is added to <code style="color:#077f11">completedPaths</code>. Completed paths are rendered using the <code style="color:#a00704">Path</code> component declaratively.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To draw on the <code style="color:#b51052">SkiaView</code> we need an instance of the internal canvas to access the methods imperatively. We do this by subscribing to the <code style="color:#2a8c00">onDraw</code> callback. In the <code style="color:#2a8c00">onDraw</code> function we store a reference to the <code style="color:#aa0b19">Canvas</code>. We are also storing <code style="color:#d6026c">width</code> &amp; <code style="color:#b711c6">height</code> of the Canvas in the global state.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The <code style="color:#8c0b0f">Drawing</code> component is rendered in <code style="color:#d90be8">App.tsx</code> like so:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-tsx"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">import</span> <span class="token imports"><span class="token maybe-class-name">React</span></span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span><span class="token keyword">import</span> <span class="token imports"><span class="token maybe-class-name">Drawing</span></span> <span class="token keyword">from</span> <span class="token string">'./src/drawing'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>
<span class="ln-num" data-num="4"></span><span class="token keyword">const</span> <span class="token function-variable function">App</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span>  <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Drawing</span></span> <span class="token punctuation">/&gt;</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>
<span class="ln-num" data-num="8"></span><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token maybe-class-name">App</span><span class="token punctuation">;</span></code></pre></div>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">Subscribing to touch events with <code style="color:#048925">useTouchHandler</code> hook</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To be able to draw on the <code style="color:#b51052">SkiaView</code> we have to subscribe to the touch events on the <code style="color:#b51052">SkiaView</code>. Whenever the user touches on the canvas and moves their finger, the <code style="color:#2a8c00">onDraw</code> callback is called with the relevant touch-point data. We will pass this data to our <code style="color:#3c7f06">touchHandler</code> which is a hook provided by <code style="color:#dd0d6b">react-native-skia</code>.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>  <span class="token keyword">const</span> touchHandler <span class="token operator">=</span> <span class="token function">useTouchHandler</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>    onActive<span class="token operator">:</span> onDrawingActive<span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span>    onStart<span class="token operator">:</span> onDrawingStart<span class="token punctuation">,</span>
<span class="ln-num" data-num="4"></span>    onEnd<span class="token operator">:</span> onDrawingFinished<span class="token punctuation">,</span>
<span class="ln-num" data-num="5"></span>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>
<span class="ln-num" data-num="7"></span>  <span class="token keyword">const</span> onDraw <span class="token operator">=</span> <span class="token function">useDrawCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span>_canvas<span class="token punctuation">,</span> info<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="8"></span>    <span class="token function">touchHandler</span><span class="token punctuation">(</span>info<span class="token punctuation">.</span>touches<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span><span class="token operator">...</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We call the <code style="color:#3c7f06">touchHandler</code> function every time onDraw callback is called with data about the current touch.</p>
<h5 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">The <code style="color:#46930b">onDrawingStart</code> callback</h5>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As soon as the user touches the screen, <code style="color:#46930b">onDrawingStart</code> is called.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> onDrawingStart <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="2"></span>  <span class="token punctuation">(</span>touchInfo<span class="token operator">:</span> TouchInfo<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="3"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>currentPath<span class="token punctuation">.</span>current<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span>    <span class="token keyword">const</span> <span class="token punctuation">{</span> x<span class="token punctuation">,</span> y <span class="token punctuation">}</span> <span class="token operator">=</span> touchInfo<span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span>    currentPath<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="6"></span>      path<span class="token operator">:</span> Skia<span class="token punctuation">.</span>Path<span class="token punctuation">.</span><span class="token function">Make</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="7"></span>      paint<span class="token operator">:</span> stroke<span class="token punctuation">.</span><span class="token function">copy</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="ln-num" data-num="8"></span>    <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span>
<span class="ln-num" data-num="10"></span>    touchState<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span>    currentPath<span class="token punctuation">.</span>current<span class="token punctuation">.</span>path<span class="token operator">?.</span><span class="token function">moveTo</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="12"></span>
<span class="ln-num" data-num="13"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>currentPath<span class="token punctuation">.</span>current<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="14"></span>      canvas<span class="token punctuation">.</span>current<span class="token operator">?.</span><span class="token function">drawPath</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="15"></span>        currentPath<span class="token punctuation">.</span>current<span class="token punctuation">.</span>path<span class="token punctuation">,</span>
<span class="ln-num" data-num="16"></span>        currentPath<span class="token punctuation">.</span>current<span class="token punctuation">.</span>paint
<span class="ln-num" data-num="17"></span>      <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="18"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="19"></span>  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="20"></span>  <span class="token punctuation">[</span>stroke<span class="token punctuation">]</span>
<span class="ln-num" data-num="21"></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We get the current <code style="color:#c40bbe">x</code> &amp; <code style="color:#bc0960">y</code> position of touch on the <code style="color:#b51052">SkiaView</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We update the <code style="color:#7b9903">currentPath</code> ref with a new path.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We set the <code style="color:#068233">touchState</code> ref to <code style="color:#e00280">true</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We move the pen in <code style="color:#7b9903">currentPath</code> to <code style="color:#e002e8">x/y</code> position with <code style="color:#4da50e">moveTo</code> function so that drawing starts from this point.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Finally we draw the path on canvas with <code style="color:#016d2a">drawPath</code> function.</li>
</ol>
<h5 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">The <code style="color:#44820b">onDrawingActive</code> callback</h5>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">While the user moves finger on the screen, we update the path based on the movement.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> onDrawingActive <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span>touchInfo<span class="token operator">:</span> ExtendedTouchInfo<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token keyword">const</span> <span class="token punctuation">{</span> x<span class="token punctuation">,</span> y <span class="token punctuation">}</span> <span class="token operator">=</span> touchInfo<span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>currentPath<span class="token punctuation">.</span>current<span class="token operator">?.</span>path<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>touchState<span class="token punctuation">.</span>current<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span>    currentPath<span class="token punctuation">.</span>current<span class="token punctuation">.</span>path<span class="token punctuation">.</span><span class="token function">lineTo</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>currentPath<span class="token punctuation">.</span>current<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="7"></span>      canvas<span class="token punctuation">.</span>current<span class="token operator">?.</span><span class="token function">drawPath</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="8"></span>        currentPath<span class="token punctuation">.</span>current<span class="token punctuation">.</span>path<span class="token punctuation">,</span>
<span class="ln-num" data-num="9"></span>        currentPath<span class="token punctuation">.</span>current<span class="token punctuation">.</span>paint
<span class="ln-num" data-num="10"></span>      <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="12"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="13"></span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We get the current <code style="color:#c40bbe">x</code> &amp; <code style="color:#bc0960">y</code> position of touch on the <code style="color:#b51052">SkiaView</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We create the path using <code style="color:#007536">lineTo</code> function to where the touch has moved.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Finally we draw the path on canvas with <code style="color:#016d2a">drawPath</code> function. It takes two values. the current path and stroke information. The <code style="color:#b100c1">stroke</code> contains information about the <code style="color:#ed177e">color</code>, <code style="color:#d6026c">width</code> &amp; <code style="color:#cf14e0">size</code> of the stroke.</li>
</ol>
<h5 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">The <code style="color:#4b9601">onDrawingFinished</code> callback</h5>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When the user raises the finger from the screen, we store the current path in global state using the <code style="color:#048c4f">updatePaths</code> function and reset <code style="color:#068233">touchState</code> and <code style="color:#7b9903">currentPath</code>.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> onDrawingFinished <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token function">updatePaths</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>  <span class="token comment">// reset the path. prepare for the next draw</span>
<span class="ln-num" data-num="4"></span>  currentPath<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span>  <span class="token comment">// set touchState to false</span>
<span class="ln-num" data-num="6"></span>  touchState<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>completedPaths<span class="token punctuation">.</span>length<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span>
<span class="ln-num" data-num="9"></span><span class="token keyword">const</span> <span class="token function-variable function">updatePaths</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="10"></span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>currentPath<span class="token punctuation">.</span>current<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span>  <span class="token comment">// Copy paths in global state</span>
<span class="ln-num" data-num="12"></span>  <span class="token keyword">let</span> updatedPaths <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span>completedPaths<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>
<span class="ln-num" data-num="14"></span>  <span class="token comment">// Push the newly created path</span>
<span class="ln-num" data-num="15"></span>  updatedPaths<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="16"></span>    path<span class="token operator">:</span> currentPath<span class="token punctuation">.</span>current<span class="token operator">?.</span>path<span class="token punctuation">.</span><span class="token function">copy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="17"></span>    paint<span class="token operator">:</span> currentPath<span class="token punctuation">.</span>current<span class="token operator">?.</span>paint<span class="token punctuation">.</span><span class="token function">copy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="18"></span>    <span class="token comment">// The current color of the stroke</span>
<span class="ln-num" data-num="19"></span>    color<span class="token operator">:</span> useDrawingStore<span class="token punctuation">.</span><span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>color
<span class="ln-num" data-num="20"></span>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="21"></span>
<span class="ln-num" data-num="22"></span>  <span class="token comment">// Update history (will get to this later in the blog)</span>
<span class="ln-num" data-num="23"></span>  history<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>currentPath<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="24"></span>
<span class="ln-num" data-num="25"></span>  <span class="token comment">// Update the state.</span>
<span class="ln-num" data-num="26"></span>  <span class="token function">setCompletedPaths</span><span class="token punctuation">(</span>updatedPaths<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="27"></span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Once the paths update, They are rendered on the <code style="color:#aa0b19">Canvas</code> below <code style="color:#b51052">SkiaView</code> as I mentioned above. We can also draw all the paths on the same <code style="color:#b51052">SkiaView</code> but it will be too intensive because all paths need to be redrawn every time <code style="color:#2a8c00">onDraw</code> is called.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">So far you'll be able to draw multiple paths freely:</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/hello_world_drawing_d.B_EFZpqc.webp 400w, https://blog.notesnook.com/assets/static/hello_world_drawing_d.DenVyaar.webp 600w, https://blog.notesnook.com/assets/static/hello_world_drawing_d.oLi5nGT2.webp 800w, https://blog.notesnook.com/assets/static/hello_world_drawing_d.CcQlfj2r.webp 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/hello_world_drawing_d.kt8OZSkz.png 400w, https://blog.notesnook.com/assets/static/hello_world_drawing_d.C4AgNILE.png 600w, https://blog.notesnook.com/assets/static/hello_world_drawing_d.Bn0zwEmc.png 800w, https://blog.notesnook.com/assets/static/hello_world_drawing_d.C0lzgut1.png 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/hello_world_drawing_d.kt8OZSkz.png" srcset="https://blog.notesnook.com/assets/static/hello_world_drawing_d.kt8OZSkz.png 400w, https://blog.notesnook.com/assets/static/hello_world_drawing_d.C4AgNILE.png 600w, https://blog.notesnook.com/assets/static/hello_world_drawing_d.Bn0zwEmc.png 800w, https://blog.notesnook.com/assets/static/hello_world_drawing_d.C0lzgut1.png 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Drawing with React Native using React Native Skia" class="bdr_default w_full"></picture></figure></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">3. Toolbar</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Our drawing app has a neat toolbar at the bottom having 3 main functions.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Set the color.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Set the stroke width.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Generate a stroke based on current color &amp; width.</li>
</ol>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/toolbar.DOwM8YJP.webp 640w, https://blog.notesnook.com/assets/static/toolbar.Cb67B-Ia.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/toolbar.CPQYnXWm.png 640w, https://blog.notesnook.com/assets/static/toolbar.DqUiUtBU.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/toolbar.CPQYnXWm.png" srcset="https://blog.notesnook.com/assets/static/toolbar.CPQYnXWm.png 640w, https://blog.notesnook.com/assets/static/toolbar.DqUiUtBU.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Native bindings not found error" class="bdr_default w_full"></picture></figure></div>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-tsx"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">import</span> <span class="token imports"><span class="token maybe-class-name">React</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> useState <span class="token punctuation">}</span></span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span><span class="token keyword">import</span> <span class="token imports"><span class="token punctuation">{</span> <span class="token maybe-class-name">StyleSheet</span><span class="token punctuation">,</span> <span class="token maybe-class-name">View</span> <span class="token punctuation">}</span></span> <span class="token keyword">from</span> <span class="token string">'react-native'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span><span class="token keyword">import</span> <span class="token imports"><span class="token maybe-class-name">Color</span></span> <span class="token keyword">from</span> <span class="token string">'../components/color'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span><span class="token keyword">import</span> <span class="token imports"><span class="token maybe-class-name">Stroke</span></span> <span class="token keyword">from</span> <span class="token string">'../components/stroke'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span><span class="token keyword">import</span> <span class="token imports">useDrawingStore</span> <span class="token keyword">from</span> <span class="token string">'../store'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span><span class="token keyword">import</span> <span class="token imports">constants</span> <span class="token keyword">from</span> <span class="token string">'../drawing/constants'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span><span class="token keyword">import</span> <span class="token imports">utils</span> <span class="token keyword">from</span> <span class="token string">'../drawing/utils'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span>
<span class="ln-num" data-num="9"></span><span class="token keyword">const</span> <span class="token function-variable function">Toolbar</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="10"></span>  <span class="token keyword">const</span> currentColor <span class="token operator">=</span> <span class="token function">useDrawingStore</span><span class="token punctuation">(</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> state<span class="token punctuation">.</span><span class="token property-access">color</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span>  <span class="token keyword">const</span> currentStrokeWidth <span class="token operator">=</span> <span class="token function">useDrawingStore</span><span class="token punctuation">(</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> state<span class="token punctuation">.</span><span class="token property-access">strokeWidth</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="12"></span>  <span class="token keyword">const</span> setStrokeWidth <span class="token operator">=</span> <span class="token function">useDrawingStore</span><span class="token punctuation">(</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> state<span class="token punctuation">.</span><span class="token property-access">setStrokeWidth</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>  <span class="token keyword">const</span> setColor <span class="token operator">=</span> <span class="token function">useDrawingStore</span><span class="token punctuation">(</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> state<span class="token punctuation">.</span><span class="token property-access">setColor</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="14"></span>  <span class="token keyword">const</span> setStroke <span class="token operator">=</span> <span class="token function">useDrawingStore</span><span class="token punctuation">(</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> state<span class="token punctuation">.</span><span class="token property-access">setStroke</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>  <span class="token keyword">const</span> <span class="token punctuation">[</span>showStrokes<span class="token punctuation">,</span> setShowStrokes<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="16"></span>
<span class="ln-num" data-num="17"></span>  <span class="token keyword">const</span> <span class="token function-variable function">onStrokeChange</span> <span class="token operator">=</span> <span class="token punctuation">(</span>stroke<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="18"></span>    <span class="token function">setStrokeWidth</span><span class="token punctuation">(</span>stroke<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="19"></span>    <span class="token function">setShowStrokes</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="20"></span>    <span class="token function">setStroke</span><span class="token punctuation">(</span>utils<span class="token punctuation">.</span><span class="token method function property-access">getPaint</span><span class="token punctuation">(</span>stroke<span class="token punctuation">,</span> currentColor<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="21"></span>  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="22"></span>
<span class="ln-num" data-num="23"></span>  <span class="token keyword">const</span> <span class="token function-variable function">onChangeColor</span> <span class="token operator">=</span> <span class="token punctuation">(</span>color<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="24"></span>    <span class="token function">setColor</span><span class="token punctuation">(</span>color<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="25"></span>    <span class="token function">setStroke</span><span class="token punctuation">(</span>utils<span class="token punctuation">.</span><span class="token method function property-access">getPaint</span><span class="token punctuation">(</span>currentStrokeWidth<span class="token punctuation">,</span> color<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="26"></span>  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="27"></span>
<span class="ln-num" data-num="28"></span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="29"></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="30"></span>      </span><span class="token punctuation">{</span>showStrokes <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="31"></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">View</span></span>
<span class="ln-num" data-num="32"></span>          <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">[</span>
<span class="ln-num" data-num="33"></span>            styles<span class="token punctuation">.</span><span class="token property-access">toolbar</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="34"></span>            <span class="token punctuation">{</span>
<span class="ln-num" data-num="35"></span>              bottom<span class="token operator">:</span> <span class="token number">80</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="36"></span>              position<span class="token operator">:</span> <span class="token string">'absolute'</span>
<span class="ln-num" data-num="37"></span>            <span class="token punctuation">}</span>
<span class="ln-num" data-num="38"></span>          <span class="token punctuation">]</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="39"></span>        <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="40"></span>          </span><span class="token punctuation">{</span>constants<span class="token punctuation">.</span><span class="token property-access">strokes</span><span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>stroke<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="41"></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Stroke</span></span>
<span class="ln-num" data-num="42"></span>              <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>stroke<span class="token punctuation">}</span></span>
<span class="ln-num" data-num="43"></span>              <span class="token attr-name">stroke</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>stroke<span class="token punctuation">}</span></span>
<span class="ln-num" data-num="44"></span>              <span class="token attr-name">onPress</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">onStrokeChange</span><span class="token punctuation">(</span>stroke<span class="token punctuation">)</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="45"></span>            <span class="token punctuation">/&gt;</span></span>
<span class="ln-num" data-num="46"></span>          <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
<span class="ln-num" data-num="47"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">View</span></span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="48"></span>      <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
<span class="ln-num" data-num="49"></span>
<span class="ln-num" data-num="50"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">View</span></span>
<span class="ln-num" data-num="51"></span>        <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">[</span>styles<span class="token punctuation">.</span><span class="token property-access">toolbar</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> position<span class="token operator">:</span> <span class="token string">'relative'</span><span class="token punctuation">,</span> marginVertical<span class="token operator">:</span> <span class="token number">12</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="52"></span>      <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="53"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">View</span></span>
<span class="ln-num" data-num="54"></span>          <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="55"></span>            backgroundColor<span class="token operator">:</span> <span class="token string">'#f7f7f7'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="56"></span>            borderRadius<span class="token operator">:</span> <span class="token number">5</span>
<span class="ln-num" data-num="57"></span>          <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="58"></span>        <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="59"></span>          </span><span class="token punctuation">{</span>showStrokes <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="60"></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">View</span></span>
<span class="ln-num" data-num="61"></span>              <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="62"></span>                width<span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="63"></span>                height<span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="64"></span>                borderRadius<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="65"></span>                backgroundColor<span class="token operator">:</span> <span class="token string">'black'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="66"></span>                alignSelf<span class="token operator">:</span> <span class="token string">'center'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="67"></span>                position<span class="token operator">:</span> <span class="token string">'absolute'</span>
<span class="ln-num" data-num="68"></span>              <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="69"></span>            <span class="token punctuation">/&gt;</span></span>
<span class="ln-num" data-num="70"></span>          <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
<span class="ln-num" data-num="71"></span>
<span class="ln-num" data-num="72"></span>          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Stroke</span></span>
<span class="ln-num" data-num="73"></span>            <span class="token attr-name">stroke</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>currentStrokeWidth<span class="token punctuation">}</span></span>
<span class="ln-num" data-num="74"></span>            <span class="token attr-name">onPress</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">setShowStrokes</span><span class="token punctuation">(</span><span class="token operator">!</span>showStrokes<span class="token punctuation">)</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="75"></span>          <span class="token punctuation">/&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="76"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">View</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="77"></span>
<span class="ln-num" data-num="78"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">View</span></span>
<span class="ln-num" data-num="79"></span>          <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="80"></span>            height<span class="token operator">:</span> <span class="token number">30</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="81"></span>            borderWidth<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="82"></span>            borderColor<span class="token operator">:</span> <span class="token string">'#f0f0f0'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="83"></span>            marginHorizontal<span class="token operator">:</span> <span class="token number">10</span>
<span class="ln-num" data-num="84"></span>          <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="85"></span>        <span class="token punctuation">/&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="86"></span>
<span class="ln-num" data-num="87"></span>        </span><span class="token punctuation">{</span>constants<span class="token punctuation">.</span><span class="token property-access">colors</span><span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="88"></span>          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Color</span></span> <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>item<span class="token punctuation">}</span></span> <span class="token attr-name">color</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>item<span class="token punctuation">}</span></span> <span class="token attr-name">onPress</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">onChangeColor</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="ln-num" data-num="89"></span>        <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
<span class="ln-num" data-num="90"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">View</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="91"></span>    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="92"></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="93"></span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="94"></span>
<span class="ln-num" data-num="95"></span><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token maybe-class-name">Toolbar</span><span class="token punctuation">;</span></code></pre></div>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">Generating a stroke</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You must be wondering what <code style="color:#e20972">utils.getPaint</code> does. The <code style="color:#127205">getPaint</code> function is responsible for generating different kind of strokes using <code style="color:#c1117b">Skia.Paint</code>:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">import</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  PaintStyle<span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span>  Skia<span class="token punctuation">,</span>
<span class="ln-num" data-num="4"></span>  StrokeCap<span class="token punctuation">,</span>
<span class="ln-num" data-num="5"></span>  StrokeJoin
<span class="ln-num" data-num="6"></span><span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@shopify/react-native-skia'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>
<span class="ln-num" data-num="8"></span><span class="token keyword">const</span> <span class="token function-variable function">getPaint</span> <span class="token operator">=</span> <span class="token punctuation">(</span>strokeWidth<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> color<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="9"></span>  <span class="token keyword">const</span> paint <span class="token operator">=</span> Skia<span class="token punctuation">.</span><span class="token function">Paint</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="10"></span>  paint<span class="token punctuation">.</span><span class="token function">setStrokeWidth</span><span class="token punctuation">(</span>strokeWidth<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span>  paint<span class="token punctuation">.</span><span class="token function">setStrokeMiter</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="12"></span>  paint<span class="token punctuation">.</span><span class="token function">setStyle</span><span class="token punctuation">(</span>PaintStyle<span class="token punctuation">.</span>Stroke<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>  paint<span class="token punctuation">.</span><span class="token function">setStrokeCap</span><span class="token punctuation">(</span>StrokeCap<span class="token punctuation">.</span>Round<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="14"></span>  paint<span class="token punctuation">.</span><span class="token function">setStrokeJoin</span><span class="token punctuation">(</span>StrokeJoin<span class="token punctuation">.</span>Round<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>  paint<span class="token punctuation">.</span><span class="token function">setAntiAlias</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="16"></span>  <span class="token keyword">const</span> _color <span class="token operator">=</span> paint<span class="token punctuation">.</span><span class="token function">copy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="17"></span>  _color<span class="token punctuation">.</span><span class="token function">setColor</span><span class="token punctuation">(</span>Skia<span class="token punctuation">.</span><span class="token function">Color</span><span class="token punctuation">(</span>color<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="18"></span>  <span class="token keyword">return</span> _color<span class="token punctuation">;</span>
<span class="ln-num" data-num="19"></span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Wherever needed, we can update the current stroke of the next path that is drawn on canvas:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token function">setStroke</span><span class="token punctuation">(</span>utils<span class="token punctuation">.</span><span class="token function">getPaint</span><span class="token punctuation">(</span>currentStrokeWidth<span class="token punctuation">,</span> item<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/stroke-examples.BymmKTiJ.webp 400w, https://blog.notesnook.com/assets/static/stroke-examples.CKk8ChO7.webp 600w, https://blog.notesnook.com/assets/static/stroke-examples.BdMdVuSG.webp 800w, https://blog.notesnook.com/assets/static/stroke-examples.BHhgo3If.webp 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/stroke-examples.BthiJgFM.png 400w, https://blog.notesnook.com/assets/static/stroke-examples.Cs7LLym8.png 600w, https://blog.notesnook.com/assets/static/stroke-examples.DCIqoyDJ.png 800w, https://blog.notesnook.com/assets/static/stroke-examples.KG8z2qyp.png 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/stroke-examples.BthiJgFM.png" srcset="https://blog.notesnook.com/assets/static/stroke-examples.BthiJgFM.png 400w, https://blog.notesnook.com/assets/static/stroke-examples.Cs7LLym8.png 600w, https://blog.notesnook.com/assets/static/stroke-examples.DCIqoyDJ.png 800w, https://blog.notesnook.com/assets/static/stroke-examples.KG8z2qyp.png 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Various lines with different strokes drawn on the canvas using React Native Skia" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Various lines with different strokes drawn on the canvas using React Native Skia</figcaption></figure></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">4. Header</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The header component has buttons for undo, redo, reset and save.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-tsx"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">import</span> <span class="token imports"><span class="token maybe-class-name">React</span></span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span><span class="token keyword">import</span> <span class="token imports"><span class="token punctuation">{</span> <span class="token maybe-class-name">Text</span><span class="token punctuation">,</span> <span class="token maybe-class-name">View</span><span class="token punctuation">,</span> <span class="token maybe-class-name">TouchableOpacity</span><span class="token punctuation">,</span> <span class="token maybe-class-name">StyleSheet</span> <span class="token punctuation">}</span></span> <span class="token keyword">from</span> <span class="token string">'react-native'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span><span class="token keyword">import</span> <span class="token imports">useDrawingStore</span> <span class="token keyword">from</span> <span class="token string">'../store'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span><span class="token keyword">import</span> <span class="token imports">history</span> <span class="token keyword">from</span> <span class="token string">'../drawing/history'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span><span class="token keyword">import</span> <span class="token imports">utils</span> <span class="token keyword">from</span> <span class="token string">'../drawing/utils'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>
<span class="ln-num" data-num="7"></span><span class="token keyword">const</span> <span class="token function-variable function">Header</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="8"></span>  <span class="token comment">/**
<span class="ln-num" data-num="9"></span>   * Reset the canvas &amp; draw state
<span class="ln-num" data-num="10"></span>   */</span>
<span class="ln-num" data-num="11"></span>  <span class="token keyword">const</span> <span class="token function-variable function">reset</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="12"></span>    useDrawingStore<span class="token punctuation">.</span><span class="token method function property-access">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">setCompletedPaths</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>    useDrawingStore<span class="token punctuation">.</span><span class="token method function property-access">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">setStroke</span><span class="token punctuation">(</span>utils<span class="token punctuation">.</span><span class="token method function property-access">getPaint</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token string">'black'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="14"></span>    useDrawingStore<span class="token punctuation">.</span><span class="token method function property-access">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">setColor</span><span class="token punctuation">(</span><span class="token string">'black'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>    useDrawingStore<span class="token punctuation">.</span><span class="token method function property-access">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">setStrokeWidth</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="16"></span>    history<span class="token punctuation">.</span><span class="token method function property-access">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="17"></span>  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="18"></span>
<span class="ln-num" data-num="19"></span>  <span class="token keyword">const</span> <span class="token function-variable function">save</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="20"></span>    <span class="token keyword">let</span> canvasInfo <span class="token operator">=</span> useDrawingStore<span class="token punctuation">.</span><span class="token method function property-access">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token property-access">canvasInfo</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="21"></span>    <span class="token keyword">let</span> paths <span class="token operator">=</span> useDrawingStore<span class="token punctuation">.</span><span class="token method function property-access">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token property-access">completedPaths</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="22"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>paths<span class="token punctuation">.</span><span class="token property-access">length</span> <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="23"></span>    <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'saving'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="24"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>canvasInfo<span class="token operator">?.</span>width <span class="token operator">&amp;&amp;</span> canvasInfo<span class="token operator">?.</span>height<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="25"></span>      <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="26"></span>        utils<span class="token punctuation">.</span><span class="token method function property-access">makeSvgFromPaths</span><span class="token punctuation">(</span>paths<span class="token punctuation">,</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="27"></span>          width<span class="token operator">:</span> canvasInfo<span class="token punctuation">.</span><span class="token property-access">width</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="28"></span>          height<span class="token operator">:</span> canvasInfo<span class="token punctuation">.</span><span class="token property-access">height</span>
<span class="ln-num" data-num="29"></span>        <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="ln-num" data-num="30"></span>      <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="31"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="32"></span>  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="33"></span>
<span class="ln-num" data-num="34"></span>  <span class="token keyword">const</span> <span class="token function-variable function">undo</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="35"></span>    history<span class="token punctuation">.</span><span class="token method function property-access">undo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="36"></span>  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="37"></span>
<span class="ln-num" data-num="38"></span>  <span class="token keyword">const</span> <span class="token function-variable function">redo</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="39"></span>    history<span class="token punctuation">.</span><span class="token method function property-access">redo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="40"></span>  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="41"></span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="42"></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">View</span></span>
<span class="ln-num" data-num="43"></span>      <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="44"></span>        height<span class="token operator">:</span> <span class="token number">50</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="45"></span>        width<span class="token operator">:</span> <span class="token string">'100%'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="46"></span>        paddingHorizontal<span class="token operator">:</span> <span class="token number">12</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="47"></span>        flexDirection<span class="token operator">:</span> <span class="token string">'row'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="48"></span>        justifyContent<span class="token operator">:</span> <span class="token string">'space-between'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="49"></span>        alignItems<span class="token operator">:</span> <span class="token string">'center'</span>
<span class="ln-num" data-num="50"></span>      <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="51"></span>    <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="52"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">View</span></span>
<span class="ln-num" data-num="53"></span>        <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="54"></span>          flexDirection<span class="token operator">:</span> <span class="token string">'row'</span>
<span class="ln-num" data-num="55"></span>        <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="56"></span>      <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="57"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">TouchableOpacity</span></span>
<span class="ln-num" data-num="58"></span>          <span class="token attr-name">activeOpacity</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">0.6</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="59"></span>          <span class="token attr-name">onPress</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>undo<span class="token punctuation">}</span></span>
<span class="ln-num" data-num="60"></span>          <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">[</span>styles<span class="token punctuation">.</span><span class="token property-access">button</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> marginRight<span class="token operator">:</span> <span class="token number">10</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="61"></span>        <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="62"></span>          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Text</span></span> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>styles<span class="token punctuation">.</span><span class="token property-access">buttonText</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Undo</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Text</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="63"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">TouchableOpacity</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="64"></span>
<span class="ln-num" data-num="65"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">TouchableOpacity</span></span>
<span class="ln-num" data-num="66"></span>          <span class="token attr-name">onPress</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>redo<span class="token punctuation">}</span></span>
<span class="ln-num" data-num="67"></span>          <span class="token attr-name">activeOpacity</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">0.6</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="68"></span>          <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>styles<span class="token punctuation">.</span><span class="token property-access">button</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="69"></span>        <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="70"></span>          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Text</span></span> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>styles<span class="token punctuation">.</span><span class="token property-access">buttonText</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Redo</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Text</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="71"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">TouchableOpacity</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="72"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">View</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="73"></span>
<span class="ln-num" data-num="74"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">View</span></span>
<span class="ln-num" data-num="75"></span>        <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="ln-num" data-num="76"></span>          flexDirection<span class="token operator">:</span> <span class="token string">'row'</span>
<span class="ln-num" data-num="77"></span>        <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="78"></span>      <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="79"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">TouchableOpacity</span></span>
<span class="ln-num" data-num="80"></span>          <span class="token attr-name">onPress</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>reset<span class="token punctuation">}</span></span>
<span class="ln-num" data-num="81"></span>          <span class="token attr-name">activeOpacity</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">0.6</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="82"></span>          <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>styles<span class="token punctuation">.</span><span class="token property-access">button</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="83"></span>        <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="84"></span>          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Text</span></span> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>styles<span class="token punctuation">.</span><span class="token property-access">buttonText</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Reset</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Text</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="85"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">TouchableOpacity</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="86"></span>
<span class="ln-num" data-num="87"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">TouchableOpacity</span></span>
<span class="ln-num" data-num="88"></span>          <span class="token attr-name">activeOpacity</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">0.6</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="89"></span>          <span class="token attr-name">onPress</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>save<span class="token punctuation">}</span></span>
<span class="ln-num" data-num="90"></span>          <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">[</span>styles<span class="token punctuation">.</span><span class="token property-access">button</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> marginLeft<span class="token operator">:</span> <span class="token number">10</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="91"></span>        <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="92"></span>          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Text</span></span> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>styles<span class="token punctuation">.</span><span class="token property-access">buttonText</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Save</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Text</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="93"></span>        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">TouchableOpacity</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="94"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">View</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="95"></span>    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">View</span></span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="96"></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="97"></span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="98"></span>
<span class="ln-num" data-num="99"></span><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token maybe-class-name">Header</span><span class="token punctuation">;</span></code></pre></div>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Undo: Removes the last added path in <code style="color:#077f11">completedPaths</code> array.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Redo: Re-adds a path removed by <code style="color:#dd0da9">undo</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Reset: Resets the drawing board to default state.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Save: Generate an SVG from all the paths in <code style="color:#077f11">completedPaths</code>.</li>
</ol>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/header.BqofRa8N.webp 640w, https://blog.notesnook.com/assets/static/header.BSsGpgmX.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/header.Dx4xiyOO.png 640w, https://blog.notesnook.com/assets/static/header.Igo6pD-L.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/header.Dx4xiyOO.png" srcset="https://blog.notesnook.com/assets/static/header.Dx4xiyOO.png 640w, https://blog.notesnook.com/assets/static/header.Igo6pD-L.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Native bindings not found error" class="bdr_default w_full"></picture></figure></div>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">History</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">History keeps track of all the changes on the drawing board.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">import</span> useDrawingStore<span class="token punctuation">,</span> <span class="token punctuation">{</span> CurrentPath <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../store'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span><span class="token keyword">const</span> history<span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="4"></span>  undo<span class="token operator">:</span> CurrentPath<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span>  redo<span class="token operator">:</span> CurrentPath<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span><span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="7"></span>  undo<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="8"></span>  redo<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="ln-num" data-num="9"></span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="10"></span>
<span class="ln-num" data-num="11"></span><span class="token comment">// Clear undo/redo stacks</span>
<span class="ln-num" data-num="12"></span><span class="token keyword">function</span> <span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="13"></span>  history<span class="token punctuation">.</span>undo <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="14"></span>  history<span class="token punctuation">.</span>redo <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span><span class="token punctuation">}</span>
<span class="ln-num" data-num="16"></span>
<span class="ln-num" data-num="17"></span><span class="token comment">// Push a new path to undo stack</span>
<span class="ln-num" data-num="18"></span><span class="token keyword">function</span> <span class="token function">push</span><span class="token punctuation">(</span>path<span class="token operator">:</span> CurrentPath<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="19"></span>  history<span class="token punctuation">.</span>undo<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="20"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When users stops drawing. The newly created path is added to history with <code style="color:#ab04c9">history.push</code> function.</p>
<h5 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">1. Undo</h5>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">function</span> <span class="token function">undo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>history<span class="token punctuation">.</span>undo<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>  <span class="token comment">// Get the last path in history</span>
<span class="ln-num" data-num="4"></span>  <span class="token keyword">let</span> lastPath <span class="token operator">=</span> history<span class="token punctuation">.</span>undo<span class="token punctuation">[</span>history<span class="token punctuation">.</span>undo<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span>  <span class="token comment">// Add the path to redo history</span>
<span class="ln-num" data-num="6"></span>  history<span class="token punctuation">.</span>redo<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>lastPath<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>  <span class="token comment">// Remove path from undo history</span>
<span class="ln-num" data-num="8"></span>  history<span class="token punctuation">.</span>undo<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span>history<span class="token punctuation">.</span>undo<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span>  <span class="token comment">// Update global state so the drawing board redraws</span>
<span class="ln-num" data-num="10"></span>  useDrawingStore<span class="token punctuation">.</span><span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setCompletedPaths</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token operator">...</span>history<span class="token punctuation">.</span>undo<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span><span class="token punctuation">}</span></code></pre></div>
<h5 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">2. Redo</h5>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">function</span> <span class="token function">redo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>history<span class="token punctuation">.</span>redo<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>  <span class="token comment">// Get last path from redo history</span>
<span class="ln-num" data-num="4"></span>  <span class="token keyword">let</span> lastPath <span class="token operator">=</span> history<span class="token punctuation">.</span>redo<span class="token punctuation">[</span>history<span class="token punctuation">.</span>redo<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span>  <span class="token comment">// Remove the path from redo history</span>
<span class="ln-num" data-num="6"></span>  history<span class="token punctuation">.</span>redo<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span>history<span class="token punctuation">.</span>redo<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>  <span class="token comment">// Add the path to undo history</span>
<span class="ln-num" data-num="8"></span>  history<span class="token punctuation">.</span>undo<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>lastPath<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span>  <span class="token comment">// Update the state</span>
<span class="ln-num" data-num="10"></span>  useDrawingStore<span class="token punctuation">.</span><span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setCompletedPaths</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token operator">...</span>history<span class="token punctuation">.</span>undo<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span><span class="token punctuation">}</span></code></pre></div>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">Exporting the drawing as an SVG file</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Once drawing completes, it can be exported to svg format using <code style="color:#339606">makeSvgFromPaths</code> function</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> <span class="token function-variable function">makeSvgFromPaths</span> <span class="token operator">=</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="2"></span>  paths<span class="token operator">:</span> CurrentPath<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span>  options<span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="4"></span>    width<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span>    height<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>    backgroundColor<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="8"></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="9"></span>  <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;svg width="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>options<span class="token punctuation">.</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" height="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>
<span class="ln-num" data-num="10"></span>    options<span class="token punctuation">.</span>height
<span class="ln-num" data-num="11"></span>  <span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" viewBox="0 0 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>options<span class="token punctuation">.</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>
<span class="ln-num" data-num="12"></span>    options<span class="token punctuation">.</span>height
<span class="ln-num" data-num="13"></span>  <span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" fill="none" xmlns="http://www.w3.org/2000/svg"&gt;
<span class="ln-num" data-num="14"></span>    &lt;rect width="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>options<span class="token punctuation">.</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" height="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>options<span class="token punctuation">.</span>height<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" fill="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>
<span class="ln-num" data-num="15"></span>      options<span class="token punctuation">.</span>backgroundColor <span class="token operator">||</span> <span class="token string">'white'</span>
<span class="ln-num" data-num="16"></span>    <span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"/&gt;
<span class="ln-num" data-num="17"></span>  &lt;g&gt;
<span class="ln-num" data-num="18"></span>
<span class="ln-num" data-num="19"></span>    </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>paths<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
<span class="ln-num" data-num="20"></span>      path<span class="token punctuation">.</span>paint <span class="token operator">&amp;&amp;</span> path<span class="token punctuation">.</span>path
<span class="ln-num" data-num="21"></span>        <span class="token operator">?</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;path d="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token punctuation">.</span>path<span class="token punctuation">.</span><span class="token function">toSVGString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" stroke="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>
<span class="ln-num" data-num="22"></span>            path<span class="token punctuation">.</span>color
<span class="ln-num" data-num="23"></span>          <span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" stroke-width="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token punctuation">.</span>paint<span class="token punctuation">.</span><span class="token function">getStrokeWidth</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" stroke-linecap="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token punctuation">.</span>paint<span class="token punctuation">.</span><span class="token function">getStrokeCap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" stroke-linejoin="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token punctuation">.</span>paint<span class="token punctuation">.</span><span class="token function">getStrokeJoin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"/&gt;</span><span class="token template-punctuation string">`</span></span>
<span class="ln-num" data-num="24"></span>        <span class="token operator">:</span> <span class="token string">''</span>
<span class="ln-num" data-num="25"></span>    <span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
<span class="ln-num" data-num="26"></span>    &lt;/g&gt;
<span class="ln-num" data-num="27"></span>    &lt;/svg&gt;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="28"></span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For each path in <code style="color:#077f11">completedPaths</code> array, we generate a <code style="color:#9c06ce">&lt;path/&gt;</code> svg component with stroke, color and width from the path so that it matches the exact drawing.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-ts"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> <span class="token function-variable function">save</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token comment">// Get canvas info, basically width and height. This will be the size of svg.</span>
<span class="ln-num" data-num="3"></span>  <span class="token keyword">let</span> canvasInfo <span class="token operator">=</span> useDrawingStore<span class="token punctuation">.</span><span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>canvasInfo<span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span>  <span class="token comment">// Get the paths from global state</span>
<span class="ln-num" data-num="5"></span>  <span class="token keyword">let</span> paths <span class="token operator">=</span> useDrawingStore<span class="token punctuation">.</span><span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>completedPaths<span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>paths<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>canvasInfo<span class="token operator">?.</span>width <span class="token operator">&amp;&amp;</span> canvasInfo<span class="token operator">?.</span>height<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="8"></span>    <span class="token comment">// Generate the svg. Saving the svg to a file can be done here</span>
<span class="ln-num" data-num="9"></span>    <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="10"></span>      utils<span class="token punctuation">.</span><span class="token function">makeSvgFromPaths</span><span class="token punctuation">(</span>paths<span class="token punctuation">,</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="11"></span>        width<span class="token operator">:</span> canvasInfo<span class="token punctuation">.</span>width<span class="token punctuation">,</span>
<span class="ln-num" data-num="12"></span>        height<span class="token operator">:</span> canvasInfo<span class="token punctuation">.</span>height
<span class="ln-num" data-num="13"></span>      <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="ln-num" data-num="14"></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="16"></span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Conclusion</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Imagine the possibilities, the sheer butter-smoothness that can be achieved with <code style="color:#dd0d6b">react-native-skia</code>! This was a very basic drawing app — touching only the surface of what's possible with this amazing library. Features like path selection, movement &amp; resizing or animations can be easily added.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The only issue currently is the lack of documentation. I was unable to find anything about the imperative API of <code style="color:#dd0d6b">react-native-skia</code> which forced me to delve into the codebase and figure everything out myself. But once proper documentation is finished, the whole ecosystem of React Native will change.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can find the complete code for the above tutorial on <a href="https://github.com/ammarahm-ed/drawing-board" target="_blank" class="c_accent">Github</a>. Free free to check it out &amp; play with it.</p>]]></content:encoded>
            <author>Ammar Ahmed</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.CqxR3gjL.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[NeutralinoJS: The Next Best Alternative to Electron & Tauri]]></title>
            <link>https://blog.notesnook.com/neutralinojs-next-best-alternative-to-electron-and-tauri</link>
            <guid isPermaLink="false">https://blog.notesnook.com/neutralinojs-next-best-alternative-to-electron-and-tauri</guid>
            <pubDate>Sat, 22 Jan 2022 12:01:22 GMT</pubDate>
            <description><![CDATA[NeutralinoJS was not a new idea — instead of packaging the whole Node + Chromium with every app, why not reuse the already installed browser each OS comes with?]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A few years back, the only way for web developers to enter into the Desktop app space was either Electron or NW.js. Both choices were not really choices at all --- both were huge, full of bloat, memory hogging frameworks but the solution was tempting to many. Huge companies like Microsoft, Slack, Discord invested into it but there were still many people who did not want to install 9 different versions of Chromium to run 9 different apps.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><a href="https://neutralino.js.org" target="_blank" class="c_accent">NeutralinoJS</a> was not a new idea --- instead of packaging the whole Node + Chromium with every app, why not reuse the already installed browser each OS comes with? Linux &amp; macOS have WebKit while Microsoft Windows has IE, Edge, and now Chromium. A fabulous idea in theory but complications like native support for system tray, notifications, file system access, data storage, and security made many hesitate into actually making it a thing.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Released in 2018 by a Sri Lankan programmer, <a href="https://github.com/shalithasuranga" target="_blank" class="c_accent">Shalitha Suranga</a>, NeutralinoJS was one of the first frameworks to properly support 3 different desktop platforms while making it extremely easy for web developers. 3 years later, it has its own Javascript client library, support for extensions, and a very minimal footprint (&lt; 3 MB).</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/neutralinojs.8FTh0WBK.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/neutralinojs.DPalEv3i.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/neutralinojs.DPalEv3i.png" srcset="https://blog.notesnook.com/assets/static/neutralinojs.DPalEv3i.png 640w" sizes="100vw" alt="The next best alternative to Electron &amp; Tauri" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">The next best alternative to Electron &amp; Tauri<!-- --> <!-- -->-<!-- --> <a href="https://neutralino.js.org/" class="c_info">NeutralinoJS</a></figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">While Tauri requires you to install Rust and a boatload of other things, NeutralinoJS stands on the shoulders of giants. It doesn't require learning a new language to take advantage of native capabilities. It lacks the huge npm ecosystem but supports a wide variety of system APIs under the <code style="color:#a02510">Neutralino</code> namespace. Currently it supports:</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Ease of Use</h2>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://neutralino.js.org/docs/api/init" target="_blank" class="c_accent">Neutralino.init</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://neutralino.js.org/docs/api/app" target="_blank" class="c_accent">Neutralino.app</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://neutralino.js.org/docs/api/computer" target="_blank" class="c_accent">Neutralino.computer</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://neutralino.js.org/docs/api/debug" target="_blank" class="c_accent">Neutralino.debug</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://neutralino.js.org/docs/api/filesystem" target="_blank" class="c_accent">Neutralino.filesystem</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://neutralino.js.org/docs/api/os" target="_blank" class="c_accent">Neutralino.os</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://neutralino.js.org/docs/api/storage" target="_blank" class="c_accent">Neutralino.storage</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://neutralino.js.org/docs/api/window" target="_blank" class="c_accent">Neutralino.window</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://neutralino.js.org/docs/api/events" target="_blank" class="c_accent">Neutralino.events</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://neutralino.js.org/docs/api/extensions" target="_blank" class="c_accent">Neutralino.extensions</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://neutralino.js.org/docs/api/updater" target="_blank" class="c_accent">Neutralino.updater</a></li>
</ul>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Installation</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Installing <a href="https://neutralino.js.org" target="_blank" class="c_accent">NeutralinoJS</a> is as simple as:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-bash"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token function">npm</span> i <span class="token parameter variable">-g</span> @neutralino/neu</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And a few seconds later, you should have <code style="color:#ac0ad1">neu</code> command available globally.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Hello Neutralino</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Creating a new NeutralinoJS app is even simpler:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-bash"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>neu create <span class="token operator">&lt;</span>project-name<span class="token operator">&gt;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">However, most of the times you are either integrating into an existing codebase or you want to use a frontend framework like React. This is where NeutralinoJS shines.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Run the above command in your app's codebase. It should create a directory with the same name. All you have to do after that is edit the <code style="color:#db11cd">neutralino.config.json</code> file. We'll edit 2 keys: <code style="color:#e80bd2">url</code> &amp; <code style="color:#04a007">documentRoot</code> to point them to our framework's build directory. For React, it is:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-bash"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token string">"documentRoot"</span><span class="token builtin class-name">:</span> <span class="token string">"./build/"</span>,
<span class="ln-num" data-num="2"></span><span class="token string">"url"</span><span class="token builtin class-name">:</span> <span class="token string">"/index.html"</span>,</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To start the app, enter:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-bash"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>neu run</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And your app should open in a native window.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/neutralino_preview.Bo0Ur6kp.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/neutralino_preview.mWp94cKi.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/neutralino_preview.mWp94cKi.png" srcset="https://blog.notesnook.com/assets/static/neutralino_preview.mWp94cKi.png 640w" sizes="100vw" alt="Neutralino JS app running in native window" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Neutralino JS app running in native window</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">All in all, relative to Tauri, NeutralinoJS is extremely easy to setup and use. Of course, it doesn't have nearly all the features Tauri has but you can easily add those via native extensions.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Security</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In terms of security, NeutralinoJS is a disappointment. It hosts the web app on a local server exposed on the localhost — all you have to do to access it is open a browser and go to <code style="color:#9c04d3">http://localhost:some-random-port</code>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">That's not even the worst thing. NeutralinoJS has a Javascript client library with many helpful functions to read files, write files, and access system info etc. This library is exposed on the client side as a global variable Neutralino. If you open the Developer Console in a Neutralino app, you can literally delete &amp; list files using:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-js"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> files <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">Neutralino</span><span class="token punctuation">.</span><span class="token property-access">filesystem</span><span class="token punctuation">.</span><span class="token method function property-access">readDirectory</span><span class="token punctuation">(</span><span class="token string">'.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span><span class="token keyword control-flow">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> file <span class="token keyword">of</span> files<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="3"></span>  <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">Neutralino</span><span class="token punctuation">.</span><span class="token property-access">filesystem</span><span class="token punctuation">.</span><span class="token method function property-access">removeFile</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">./</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>file<span class="token punctuation">.</span><span class="token property-access">entry</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And viola! All files in your current working directory are now gone.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/neutralino_inspect.0KdvYphd.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/neutralino_inspect.BDQPv0FM.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/neutralino_inspect.BDQPv0FM.png" srcset="https://blog.notesnook.com/assets/static/neutralino_inspect.BDQPv0FM.png 640w" sizes="100vw" alt="Listing all files in current working directory" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Listing all files in current working directory</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Very, very dangerous. I am aware that this is done for Developer experience but if, let's say, you wanted to load untrusted content in a NeutralinoJS app, I'd seriously reconsider. Most apps, however, only load local files for which NeutralinoJS is great. It'd still have been amazing if the client library wasn't exposed as a global variable.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Extensibility</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><a href="https://neutralino.js.org/docs/how-to/extensions-overview" target="_blank" class="c_accent">Extensions in NeutralinoJS</a> are simply programs that communicate over Websockets. Naturally, this allows the program to be written in any language or framework. This opens the possibility to write the backend in NodeJS, Rust or Go while the app is rendered in the native browser.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Since it's all Websockets, stuff like converting data structures, translating function arguments, reading results etc. are automatically handled by a lot of Websocket client libraries.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Publising &amp; Updates</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">NeutralinoJS has no built-in system for receiving or installing updates nor does it have a proper bundler like <code style="color:#091b8c">electron-builder</code> to make installers for each operating system. All this makes NeutralinoJS nonviable for any but the most trivial projects.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Running the <code style="color:#ad11d8">neu build -r</code> creates a .zip file containing binaries &amp; resources for all 3 platforms. This works but there's no desktop integration, no icons, no way to install etc.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Migrating from Electron</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Suffice it to say, there is 0 API compatibility with Electron. There's no node, no npm ecosystem, nothing like that. Your best bet to migrate an Electron app to Neutralino is either rewriting the backend code to a natively compiled language like Go or Rust or you can package the whole NodeJS along as a binary.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Final verdict</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Having NeutralinoJS in the Native Javascript space provides necessary competition but it falls short of a lot of things required for a native app (even a native Javascript app) --- especially security. Combine that with this being mostly a hobby project run by a single developer with little financial (or other) support from the community, and you get something that is truly wonderful but not yet there.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Initially, I had plans to migrate Notesnook from Electron to Neutralino because the feature-set of NeutralinoJS is simply amazing. However, the security &amp; reliability provided by Electron is simply unparalleled. If the developer works on bundling &amp; security, NeutralinoJS can become an amazing alternative to Tauri &amp; Electron.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.DgLmkBeW.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Scoped Storage in React Native: New Android 10 API for File System Access]]></title>
            <link>https://blog.notesnook.com/scoped-storage-in-react-native</link>
            <guid isPermaLink="false">https://blog.notesnook.com/scoped-storage-in-react-native</guid>
            <pubDate>Sat, 15 Jan 2022 12:01:22 GMT</pubDate>
            <description><![CDATA[Up until Android 10 it was possible to get access to the whole user's device. To fix this, Google introduced Scoped Storage API in Android 10.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">File system access is an essential part of making an application. Up until Android 10 any app could request file storage permissions and have access to all files stored on a user's device even if they needed access to just one folder. This was a huge security issue which was exploited time and again. To fix this, Google introduced Scoped Storage API in Android 10 --- a way for apps to access only the folders required with user's explicit permission.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Since the release of Android 10, Google has made Scoped Storage the default way to access files on an Android device but up until Android 11, it was possible to override this by putting <code style="color:#cc0468">android:requestLegacyExternalStorage="true"</code> in <code style="color:#e5026c">AndroidManifest.xml</code>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">However, this is changing fast since the start of 2021. Google is enforcing all apps to use Scoped Storage to store or read files on a user's device. <a href="https://developer.android.com/training/data-storage/manage-all-files" target="_blank" class="c_accent">Some apps can still get all files access if the pass this criteria</a>.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">How scoped storage works?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Scoped Storage API gives user full control over which app access which directories. Whenever an app needs to write files on user's device, the user must explicitly grant permission to store files to that specific folder. The app can then read/write only to that directory and it's subdirectories.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Android Scoped Storage for React Native</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">At the start of 2021 I came across <a href="https://developer.android.com/about/versions/11/privacy/storage#scoped-storage" target="_blank" class="c_accent">Google's policy update</a>:</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Apps that run on Android 11 but target Android 10 (API level 29) can still request the <code style="color:#0d9169">requestLegacyExternalStorage</code> attribute. This flag allows apps to temporarily opt out of the changes associated with scoped storage, such as granting access to different directories and different types of media files. <strong>After you update your app to target Android 11, the system ignores the <code style="color:#0d9169">requestLegacyExternalStorage</code> flag.</strong></p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Since Notesnook requires access to device storage for storing backups, note attachments, recovery keys etc., it was obvious that we will have to migrate away from legacy storage on Android sooner or later. Being proactive, I did a quick search on Google and Github expecting to find someone having already fixed the issue. Suffice it to say, I was disappointed --- reading/writing files is already a hassle in React Native and with this upcoming change, it'll only get harder. In any case, I took the matter into my own hands and created <a href="https://github.com/ammarahm-ed/react-native-scoped-storage" target="_blank" class="c_accent">react-native-scoped-storage</a>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><a href="https://github.com/ammarahm-ed/react-native-scoped-storage" target="_blank" class="c_accent">react-native-scoped-storage</a> makes it incredibly easy to access the Scoped Storage APIs in Android. If you want a quick way to migrate from legacy storage to Scoped Storage, this library is currently the only way in React Native.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In any case, let's see how the Scoped Storage API works and how you can use it in your own apps.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Getting permissions to read/write files</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Goals:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Ask the user to give us permission to a folder where we can store files without interruption.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We only want to do this once; not every session.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With the goals in mind, first step is to request permssion to a directory in user's phone:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword module">import</span> <span class="token imports"><span class="token operator">*</span> <span class="token keyword module">as</span> <span class="token maybe-class-name">ScopedStorage</span></span> <span class="token keyword module">from</span> <span class="token string">'react-native-scoped-storage'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span><span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">AsyncStorage</span></span> <span class="token keyword module">from</span> <span class="token string">'@react-native-async-storage/async-storage'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>
<span class="ln-num" data-num="4"></span><span class="token keyword">let</span> dir <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">ScopedStorage</span><span class="token punctuation">.</span><span class="token method function property-access">openDocumentTree</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This does all the heavy lifting for you. Running the app, you'll see something similar to this:</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/openDocumentTree.BGZLu491.webp 400w, https://blog.notesnook.com/assets/static/openDocumentTree.CMfA5t6s.webp 600w, https://blog.notesnook.com/assets/static/openDocumentTree.pU3YEew0.webp 800w, https://blog.notesnook.com/assets/static/openDocumentTree.BC6nj3Gp.webp 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/openDocumentTree.CGyNslBK.png 400w, https://blog.notesnook.com/assets/static/openDocumentTree.B1hepS-p.png 600w, https://blog.notesnook.com/assets/static/openDocumentTree.c2ROeT_K.png 800w, https://blog.notesnook.com/assets/static/openDocumentTree.CZKBntIa.png 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/openDocumentTree.CGyNslBK.png" srcset="https://blog.notesnook.com/assets/static/openDocumentTree.CGyNslBK.png 400w, https://blog.notesnook.com/assets/static/openDocumentTree.B1hepS-p.png 600w, https://blog.notesnook.com/assets/static/openDocumentTree.c2ROeT_K.png 800w, https://blog.notesnook.com/assets/static/openDocumentTree.CZKBntIa.png 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" class="bdr_default w_full"></picture></figure><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/openDocumentTree2.Ds7oaNAo.webp 400w, https://blog.notesnook.com/assets/static/openDocumentTree2.DU5jXQTf.webp 600w, https://blog.notesnook.com/assets/static/openDocumentTree2.DGhncyit.webp 800w, https://blog.notesnook.com/assets/static/openDocumentTree2.C5w_0a8L.webp 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/openDocumentTree2.B_P0GtHN.png 400w, https://blog.notesnook.com/assets/static/openDocumentTree2.B59vOjW3.png 600w, https://blog.notesnook.com/assets/static/openDocumentTree2.CKW0wJbs.png 800w, https://blog.notesnook.com/assets/static/openDocumentTree2.C4RkT4sA.png 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/openDocumentTree2.B_P0GtHN.png" srcset="https://blog.notesnook.com/assets/static/openDocumentTree2.B_P0GtHN.png 400w, https://blog.notesnook.com/assets/static/openDocumentTree2.B59vOjW3.png 600w, https://blog.notesnook.com/assets/static/openDocumentTree2.CKW0wJbs.png 800w, https://blog.notesnook.com/assets/static/openDocumentTree2.C4RkT4sA.png 1200w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">By default, Android limits the number of directories an app can get access to. To avoid hitting that limit, we will store the information about this directory in <code style="color:#960d0f">AsyncStorage</code> for later use.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token comment">// ....</span>
<span class="ln-num" data-num="2"></span><span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>dir<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="3"></span>  <span class="token comment">// Save information about the directory in AsyncStorage.</span>
<span class="ln-num" data-num="4"></span>  <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">AsyncStorage</span><span class="token punctuation">.</span><span class="token method function property-access">setItem</span><span class="token punctuation">(</span><span class="token string">'userDataDirectory'</span><span class="token punctuation">,</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>dir<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Permissions for this directory are now persisted. If we want to read/write a file later, we can simply fetch information about the directory from <code style="color:#960d0f">AsyncStorage</code> like so:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">let</span> dir <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">AsyncStorage</span><span class="token punctuation">.</span><span class="token method function property-access">getItem</span><span class="token punctuation">(</span><span class="token string">'userDataDirectory'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span><span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>dir<span class="token punctuation">)</span> dir <span class="token operator">=</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">parse</span><span class="token punctuation">(</span>dir<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This works for most cases but the system can revoke these permissions at anytime. To be safe, we should first check if we still have permission before doing anything:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> persistedUris <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">ScopedStorage</span><span class="token punctuation">.</span><span class="token method function property-access">getPersistedUriPermissions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// List of all uris where the app has access to read/write</span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span><span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>persistedUris<span class="token punctuation">.</span><span class="token method function property-access">indexOf</span><span class="token punctuation">(</span>dir<span class="token punctuation">.</span><span class="token property-access">uri</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="4"></span>  <span class="token comment">// Write our file</span>
<span class="ln-num" data-num="5"></span><span class="token punctuation">}</span> <span class="token keyword control-flow">else</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="6"></span>  <span class="token keyword">let</span> dir <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">ScopedStorage</span><span class="token punctuation">.</span><span class="token method function property-access">openDocumentTree</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>  <span class="token comment">// Store the new directory data in Async Storage then save our file</span>
<span class="ln-num" data-num="8"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Summing it all up:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword module">import</span> <span class="token imports"><span class="token operator">*</span> <span class="token keyword module">as</span> <span class="token maybe-class-name">ScopedStorage</span></span> <span class="token keyword module">from</span> <span class="token string">'react-native-scoped-storage'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span><span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">AsyncStorage</span></span> <span class="token keyword module">from</span> <span class="token string">'@react-native-async-storage/async-storage'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>
<span class="ln-num" data-num="4"></span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">requestPermission</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">directoryId</span><span class="token operator">:</span> string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span>  dir <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">ScopedStorage</span><span class="token punctuation">.</span><span class="token method function property-access">openDocumentTree</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dir<span class="token punctuation">)</span> <span class="token keyword control-flow">return</span> <span class="token keyword null nil">null</span><span class="token punctuation">;</span> <span class="token comment">// User cancelled</span>
<span class="ln-num" data-num="7"></span>  <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">AsyncStorage</span><span class="token punctuation">.</span><span class="token method function property-access">setItem</span><span class="token punctuation">(</span>directoryId<span class="token punctuation">,</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>dir<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span>  <span class="token keyword control-flow">return</span> dir<span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span><span class="token punctuation">}</span>
<span class="ln-num" data-num="10"></span>
<span class="ln-num" data-num="11"></span><span class="token keyword module">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getAndroidDir</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">directoryId</span><span class="token operator">:</span> string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="12"></span>  <span class="token keyword control-flow">try</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="13"></span>    <span class="token keyword">let</span> dir <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">AsyncStorage</span><span class="token punctuation">.</span><span class="token method function property-access">getItem</span><span class="token punctuation">(</span>directoryId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Check if dir exists already</span>
<span class="ln-num" data-num="14"></span>    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dir<span class="token punctuation">)</span> dir <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">requestPermission</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>    <span class="token comment">// request new permissions &amp; save the dir;</span>
<span class="ln-num" data-num="16"></span>    <span class="token keyword control-flow">else</span> dir <span class="token operator">=</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">parse</span><span class="token punctuation">(</span>dir<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="17"></span>
<span class="ln-num" data-num="18"></span>    <span class="token keyword">const</span> persistedUris <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">ScopedStorage</span><span class="token punctuation">.</span><span class="token method function property-access">getPersistedUriPermissions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// list all persisted uris</span>
<span class="ln-num" data-num="19"></span>    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>persistedUris<span class="token punctuation">.</span><span class="token method function property-access">indexOf</span><span class="token punctuation">(</span>dir<span class="token punctuation">.</span><span class="token property-access">uri</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword control-flow">return</span> dir<span class="token punctuation">;</span> <span class="token comment">// Verify we still have permission</span>
<span class="ln-num" data-num="20"></span>    <span class="token keyword control-flow">return</span> <span class="token keyword control-flow">await</span> <span class="token function">requestPermission</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// request new permissions &amp; save the dir;</span>
<span class="ln-num" data-num="21"></span>  <span class="token punctuation">}</span> <span class="token keyword control-flow">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="22"></span>    <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="23"></span>    <span class="token keyword control-flow">return</span> <span class="token keyword null nil">null</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="24"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="25"></span><span class="token punctuation">}</span></code></pre></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Revoking permissions to a specific folder</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Most apps don't need to revoke permissions once granted but if you need that, there's an API for that as well:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword control-flow">await</span> <span class="token maybe-class-name">ScopedStorage</span><span class="token punctuation">.</span><span class="token method function property-access">releasePersistableUriPermission</span><span class="token punctuation">(</span>dir<span class="token punctuation">.</span><span class="token property-access">uri</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Directories you can no longer access</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As usualy, there are some directories you cannot get permission to read/write in:</p>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The root directory of the internal storage volume.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The root directory of each SD card volume that the device manufacturer considers to be reliable, regardless of whether the card is emulated or removable. A reliable volume is one that an app can successfully access most of the time.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The root of the Download directory. (You can get permission to a subfolder inside Download).</li>
</ul>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">source: <a href="https://developer.android.com/about/versions/11/privacy/storage#directory-access" target="_blank" class="c_accent">developer.android.com</a></p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Writing a file in background</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Most often apps require access to the file system to store things in background without disturbing the user. If you have permissions to a directory, this is as simple as doing:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">let</span> text <span class="token operator">=</span> <span class="token string">'hello world'</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span><span class="token keyword control-flow">await</span> <span class="token maybe-class-name">ScopedStorage</span><span class="token punctuation">.</span><span class="token method function property-access">writeFile</span><span class="token punctuation">(</span>dir<span class="token punctuation">.</span><span class="token property-access">uri</span><span class="token punctuation">,</span> <span class="token string">'helloworld.txt'</span><span class="token punctuation">,</span> <span class="token string">'text/plain'</span><span class="token punctuation">,</span> text<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Asking user to save a file</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can save a file direcly after requesting permissions to a folder. However, in some cases you just need to save a file and its great if user can decide (and remember) where it is stored. An example use case is saving a file to Downloads folder or root of the app.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">let</span> file <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">ScopedStorage</span><span class="token punctuation">.</span><span class="token method function property-access">createDocument</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="2"></span>  <span class="token string">'helloworld.txt'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span>  <span class="token string">'text/plain'</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="4"></span>  <span class="token string">'hello world'</span>
<span class="ln-num" data-num="5"></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/createDocument.CxXUwP26.webp 400w, https://blog.notesnook.com/assets/static/createDocument.KzulNvne.webp 600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/createDocument.C1A7HPlZ.png 400w, https://blog.notesnook.com/assets/static/createDocument.Dcuo-T8R.png 600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/createDocument.C1A7HPlZ.png" srcset="https://blog.notesnook.com/assets/static/createDocument.C1A7HPlZ.png 400w, https://blog.notesnook.com/assets/static/createDocument.Dcuo-T8R.png 600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" class="bdr_default w_full"></picture></figure></div>
<div sx="[object Object]" class="d_flex"><p sx="[object Object]" style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The best thing about Scoped Storage API is that it makes it possible for a
user to give permissions to Cloud directories making it possible for any app
to sync it's data to the Cloud without any external dependency.</p><p></p></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Asking user to select a file</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We can also select a file from storage and read it's contents with scoped storage.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">let</span> file <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token maybe-class-name">ScopedStorage</span><span class="token punctuation">.</span><span class="token method function property-access">openDocument</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Conclusion</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Prior to Android 10 it was really simple to read/write data on user's device. However due to policy changes by Google, we will need migrate our apps to use Scoped Storage so that we can prevent our apps from a broken user experience. However this access will be limited to specific folders on user's device.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can play around with the exaxmple app and checkout the complete documentation of the library on <a href="https://github.com/ammarahm-ed/react-native-scoped-storage" target="_blank" class="c_accent">Github repo</a>.</p>]]></content:encoded>
            <author>Ammar Ahmed</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.B2JelNAc.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v1.6.0: Encrypted File Attachments, Notification Notes, Compact Mode & Widgets!]]></title>
            <link>https://blog.notesnook.com/notesnook-1-6-0-update</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-1-6-0-update</guid>
            <pubDate>Tue, 02 Nov 2021 12:01:22 GMT</pubDate>
            <description><![CDATA[After about a month and a half of battling with encryption, S3 APIs, bugs, and testing, we are finally here with version 1.6.0.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When we started out Notesnook, our primary goal was to make a 100% private note taking app that was simple, fast, and <em>complete</em>. Little did we know that we'd be compared with Evernote --- a note taking app 20 years in the making.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">After about a month and a half of battling with encryption, S3 APIs, bugs, and testing, we are finally here with version 1.6.0 --- a release that brings us very, very close to <em>completeness.</em></p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Encrypted File Attachments</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Encrypted attachments are fun. You can add any file from your device and be sure that no one can see it except you. With this release we are happy to announce that you can now attach any file, image, video or document in Notesnook.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have built the attachments system in such a way that all your attachments are locally shared --- which means re-attaching the same file doesn't create a duplicate.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Every Notesnook Pro user will get unlimited file storage --- the only limit is that one file must be under 500 MB in total size.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/attach_file.DP1m-H5L.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/attach_file.CcZyoMYD.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/attach_file.CcZyoMYD.png" srcset="https://blog.notesnook.com/assets/static/attach_file.CcZyoMYD.png 640w" sizes="100vw" alt="You can attach any type of file up to 500 MB in size" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Bulletproof syncing</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">One of the main capabilities of Notesnook is syncing and as many of you soon realized, it wasn't the best. There were a few problems that we addressed:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Prior to v1.6.0 images were embedded into the note which meant that if you changed even a single character it'd reupload the whole note. A sure abuse of bandwidth and processing power. This is no longer the case --- all images are now added as attachments and sync independently from a note.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Sync conflicts. Annoying, annoying, <em>annoying</em>, especially when they happen on the same device you are working on. With this release, we have improved the conflict detection logic to ensure a few things:<!-- -->
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">No conflicts on the same device, ever.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">No conflicts when you have the same note opened on another device.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">No conflicts when you are working on a different note on another device.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">All conflicts that happen under a minute of each other will be automatically resolved.</li>
</ol>
</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Faster syncing. While the automatic sync interval remains the same (5 seconds), the overall performance of syncing should be much better.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We also tracked down and stopped unnecessary sync requests (causing majority of the sync conflicts) --- this will greatly reduce the bandwidth usage.</li>
</ol>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Take a Note from Notification Drawer</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This release enables any user to take notes directly from their notification drawer. This is useful in cases where you want to quickly note down a phone number or an address without switching context.</p>
<style data-emotion="css fsju5r">.css-fsju5r{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><style data-emotion="css xmhgif">.css-xmhgif{box-sizing:border-box;margin:0;min-width:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><div class="css-xmhgif"><p style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"></p><h4 style="margin-top:0;font-weight:600">Pin Notes to Notifications</h4>
Aside from taking a note from the Notification Drawer, you can also pin any note to the Notification Drawer.<p></p><p></p></div>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/notification_pinned_note.CzKR6lzp.webp 400w, https://blog.notesnook.com/assets/static/notification_pinned_note.C2j95upi.webp 600w, https://blog.notesnook.com/assets/static/notification_pinned_note.BBuu4ds_.webp 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/jpg" srcset="https://blog.notesnook.com/assets/static/notification_pinned_note.vPIzsYLW.jpg 400w, https://blog.notesnook.com/assets/static/notification_pinned_note.DgTYN4IH.jpg 600w, https://blog.notesnook.com/assets/static/notification_pinned_note.qq2shQbr.jpg 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/notification_pinned_note.vPIzsYLW.jpg" srcset="https://blog.notesnook.com/assets/static/notification_pinned_note.vPIzsYLW.jpg 400w, https://blog.notesnook.com/assets/static/notification_pinned_note.DgTYN4IH.jpg 600w, https://blog.notesnook.com/assets/static/notification_pinned_note.qq2shQbr.jpg 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Pin an important note to notifications" class="bdr_default w_full"></picture></figure><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/take_note_notifications.bU4C95ZG.webp 400w, https://blog.notesnook.com/assets/static/take_note_notifications.h0mhhW1r.webp 600w, https://blog.notesnook.com/assets/static/take_note_notifications.BCGfiqF-.webp 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/jpg" srcset="https://blog.notesnook.com/assets/static/take_note_notifications.BoJSGHVl.jpg 400w, https://blog.notesnook.com/assets/static/take_note_notifications.BSBdVr2M.jpg 600w, https://blog.notesnook.com/assets/static/take_note_notifications.BEDjsBC8.jpg 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/take_note_notifications.BoJSGHVl.jpg" srcset="https://blog.notesnook.com/assets/static/take_note_notifications.BoJSGHVl.jpg 400w, https://blog.notesnook.com/assets/static/take_note_notifications.BSBdVr2M.jpg 600w, https://blog.notesnook.com/assets/static/take_note_notifications.BEDjsBC8.jpg 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Take notes from notifications in Notesnook" class="bdr_default w_full"></picture></figure></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Share to Note</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">While it was possible to share anything to Notesnook before, it would always be added as a new note. This wasn't especially useful if you were taking notes, say, for a book or clipping information from the Internet. In this release, we have added the ability to append anything to any note from anywhere on your phone.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/share_note_widget.D_NOO1K6.webp 400w, https://blog.notesnook.com/assets/static/share_note_widget.B_X4kvTC.webp 600w, https://blog.notesnook.com/assets/static/share_note_widget.kHV_lJGE.webp 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/jpg" srcset="https://blog.notesnook.com/assets/static/share_note_widget.bcgOzjlg.jpg 400w, https://blog.notesnook.com/assets/static/share_note_widget.DuyvuOHz.jpg 600w, https://blog.notesnook.com/assets/static/share_note_widget.B44MzqOT.jpg 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/share_note_widget.bcgOzjlg.jpg" srcset="https://blog.notesnook.com/assets/static/share_note_widget.bcgOzjlg.jpg 400w, https://blog.notesnook.com/assets/static/share_note_widget.DuyvuOHz.jpg 600w, https://blog.notesnook.com/assets/static/share_note_widget.B44MzqOT.jpg 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Clip text and other information from the web" class="bdr_default w_full"></picture></figure><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/append_to_note.ClukjnZL.webp 400w, https://blog.notesnook.com/assets/static/append_to_note.SuR1eNz0.webp 600w, https://blog.notesnook.com/assets/static/append_to_note._0Fp_sZ6.webp 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/jpg" srcset="https://blog.notesnook.com/assets/static/append_to_note.-afo-9wR.jpg 400w, https://blog.notesnook.com/assets/static/append_to_note.CLNouH7N.jpg 600w, https://blog.notesnook.com/assets/static/append_to_note.BKm5ZCKC.jpg 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/append_to_note.-afo-9wR.jpg" srcset="https://blog.notesnook.com/assets/static/append_to_note.-afo-9wR.jpg 400w, https://blog.notesnook.com/assets/static/append_to_note.CLNouH7N.jpg 600w, https://blog.notesnook.com/assets/static/append_to_note.BKm5ZCKC.jpg 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Append shared text to existing notes" class="bdr_default w_full"></picture></figure></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">New Widgets on Android &amp; iOS</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With this release we have also added new Quick Note widgets for iOS &amp; Android.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/home_widget_android.DTWqwWxY.webp 400w, https://blog.notesnook.com/assets/static/home_widget_android.Bd5-z5rI.webp 600w, https://blog.notesnook.com/assets/static/home_widget_android.CXpwciYg.webp 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/jpg" srcset="https://blog.notesnook.com/assets/static/home_widget_android.CBoRg15N.jpg 400w, https://blog.notesnook.com/assets/static/home_widget_android.uFy7_xeS.jpg 600w, https://blog.notesnook.com/assets/static/home_widget_android.DbOEs_vx.jpg 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/home_widget_android.CBoRg15N.jpg" srcset="https://blog.notesnook.com/assets/static/home_widget_android.CBoRg15N.jpg 400w, https://blog.notesnook.com/assets/static/home_widget_android.uFy7_xeS.jpg 600w, https://blog.notesnook.com/assets/static/home_widget_android.DbOEs_vx.jpg 800w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Notesnook widget for android on home screen" class="bdr_default w_full"></picture></figure><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/home_widget_ios.BHxfuzI1.webp 400w, https://blog.notesnook.com/assets/static/home_widget_ios.Ctj6AXxd.webp 600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/home_widget_ios.BYWrXCQF.png 400w, https://blog.notesnook.com/assets/static/home_widget_ios.BOrCKz4L.png 600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/home_widget_ios.BYWrXCQF.png" srcset="https://blog.notesnook.com/assets/static/home_widget_ios.BYWrXCQF.png 400w, https://blog.notesnook.com/assets/static/home_widget_ios.BOrCKz4L.png 600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="Notesnook widget on iOS on home screen" class="bdr_default w_full"></picture></figure></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">A more compact...compact mode</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Let's be honest. The compact mode on the web &amp; desktop version wasn't really compact at all. You could barely fit up to 10 notes on a single screen. With v1.6.0, the new compact mode allows you to easily fit upto 20 notes on a 1080p screen.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/compact_mode.CKvhJlaa.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/compact_mode.BzBqokCY.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/compact_mode.BzBqokCY.png" srcset="https://blog.notesnook.com/assets/static/compact_mode.BzBqokCY.png 640w" sizes="100vw" alt="A much, much more compact mode" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">A much, much more compact mode</figcaption></figure></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Try the new version</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As always, all these features are 100% cross-platform. You can install our Android &amp; iOS apps from their respective stores.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For desktop, you can go and <a href="https://notesnook.com/" target="_blank" class="c_accent">download for your platform from our website</a>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For quickly trying out the new version, you can just <a href="https://app.notesnook.com/" target="_blank" class="c_accent">use our web app.</a></p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Ready to go pro?</h2>
<style data-emotion="css fsju5r">.css-fsju5r{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><style data-emotion="css xmhgif">.css-xmhgif{box-sizing:border-box;margin:0;min-width:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><div class="css-xmhgif"><p style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><p sx="[object Object]" style="font-size:var(--font-sizes-sm)" class="c_paragraph">Get 15% off yearly and monthly plan. Use coupon code YEAR15. <a sx="[object Object]" href="https://app.notesnook.com/#/buy/YEAR15" style="font-size:var(--font-sizes-sm)" class="c_paragraph">Subscribe now</a></p><p></p></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Stay updated</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We recommend that you <a href="https://twitter.com/notesnook" target="_blank" class="c_accent">follow us on Twitter</a> and <a href="https://discord.com/invite/zQBK97EE22" target="_blank" class="c_accent">join our Discord server</a> to stay up to date with all the new features coming to Notesnook. We also provide 24/7 support to all our users on Discord.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.NXUUYeb2.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[React useRef Hook for Dummies: How to Use useRef Correctly with Examples]]></title>
            <link>https://blog.notesnook.com/react-useref-hook-with-examples</link>
            <guid isPermaLink="false">https://blog.notesnook.com/react-useref-hook-with-examples</guid>
            <pubDate>Sat, 09 Oct 2021 12:01:22 GMT</pubDate>
            <description><![CDATA[React useRef hook is much more 'useful' than you might think at first. useRef is especially useful when you need to access functions of a component.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">React is built around reactivity. You make a change or update a value, and the UI updates instantly. Almost every functional component in React has some state. Today we will discuss how we can use the React useRef hook correctly and explore different use cases for useRef.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Whenever you create a new function, you will most likely assign some state to it so that your UI would re-render whenever your data changes.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> <span class="token punctuation">[</span>counter<span class="token punctuation">,</span> setCounter<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Later if you want to update the counter:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token function">setCounter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">counter</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> counter <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And then you are representing your state somewhere in your UI.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-jsx"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword control-flow">return</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="2"></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>App<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="3"></span>    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>counter<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="4"></span>  </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="5"></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But let's say that you wanted to store a value and update it without re-rendering your component. The simplest option would be to define a variable in top level scope of your component.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Define a variable in the component scope</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Suppose that you wanted to store the previous value of <code style="color:#e2026e">counter</code> in a <code style="color:#707f01">prevCount</code> variable whenever you updated <code style="color:#e2026e">counter</code>. Normally, you'd go about it like this:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">function</span> <span class="token function"><span class="token maybe-class-name">Counter</span></span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token keyword">let</span> prevCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>  <span class="token keyword">const</span> <span class="token punctuation">[</span>counter<span class="token punctuation">,</span> setCounter<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span>
<span class="ln-num" data-num="5"></span>  <span class="token comment">// ...</span>
<span class="ln-num" data-num="6"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And when you are ready to increment your <code style="color:#e2026e">counter</code>, you'll store the previous value like this:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-jsx"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword control-flow">return</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="2"></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="3"></span>    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>counter<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="4"></span>    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span>
<span class="ln-num" data-num="5"></span>      <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="6"></span>        <span class="token function">setCounter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">counter</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="7"></span>          prevCount <span class="token operator">=</span> counter<span class="token punctuation">;</span> <span class="token comment">// store previous value in prevCount</span>
<span class="ln-num" data-num="8"></span>          <span class="token keyword control-flow">return</span> counter <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// increment</span>
<span class="ln-num" data-num="9"></span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="10"></span>      <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="11"></span>    <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="12"></span>      Increment counter
<span class="ln-num" data-num="13"></span>    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="14"></span>  </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="15"></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Looks good, no? And this should work but see what happens to the <code style="color:#707f01">prevCount</code> across component renders.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Let's use a useEffect hook to print the value of <code style="color:#707f01">prevCount</code> to the console whenever we update the <code style="color:#e2026e">counter</code> state:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'counter:'</span><span class="token punctuation">,</span> counter<span class="token punctuation">,</span> <span class="token string">'prevCount:'</span><span class="token punctuation">,</span> prevCount<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>counter<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And after building and a running:</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/counter.BNV8KYdK.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/counter.Ccr8Tvnd.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/counter.Ccr8Tvnd.png" srcset="https://blog.notesnook.com/assets/static/counter.Ccr8Tvnd.png 640w" sizes="100vw" alt="react useref hook counter example" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Wait a second! Why is <code style="color:#707f01">prevCount</code> always 0?!</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">React rendering for dummies</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A React functional component is essentially a Javascript function that returns a React component. This means that all code within it is run every time the component is rendered. This sounds inefficient but React is clever enough to persist its own state across component renders but for that to happen you, the developer, have to use things like <code style="color:#297507">useState</code>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In comparison, local variables inside a React functional component are not the responsibility of React since they are not a part of React component state. This means that whenever a component renders, like in our example when we increment the <code style="color:#e2026e">counter</code>, all the local variables will be reset.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">At this point you might be wondering, "Well, why don't we just use <code style="color:#297507">useState</code> then?" The reason is that <code style="color:#297507">useState</code> is the wrong tool for the job if the value you want to persist across renders doesn't affect it in any way. You'll be causing unnecessary re-renders every time you update the value.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">So how can you make sure your local variables are persisted across component renders without triggering any React re-renders?</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Define a variable outside component scope</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">AKA define your variable in the global scope. I know, I know, this is a bad practice for good reasons but let's consider it for the sake of completeness.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Javascript global scope is a double edged sword. Anything you define in the global scope is accessible anywhere else throughout the lifetime of the program. That means you can define a global constant in one place and update it from wherever you want. This also means that the garbage collector cannot cleanup the memory taken by the stuff in the global scope.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The only change you need to make is shift the <code style="color:#707f01">prevCount</code> variable to the global scope:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">let</span> prevCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// define prevCount in the global scope.</span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span><span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">Counter</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="4"></span><span class="token spread operator">...</span><span class="token punctuation">.</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Alright, that's simple but does it work?</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/counter_2.ipQWvkHX.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/counter_2.3KnLsoiZ.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/counter_2.3KnLsoiZ.png" srcset="https://blog.notesnook.com/assets/static/counter_2.3KnLsoiZ.png 640w" sizes="100vw" alt="react useref hook counter example" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Yep. It's working but there are a few problems with this approach:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">If the component unmounts, the value of <code style="color:#707f01">prevCount</code> will not reset to 0. You will manually need to reset the value every time --- and there is no simple way to do that.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">If you reuse your <code style="color:#890906">Counter</code> in multiple places, all the instances will reference the same <code style="color:#707f01">prevCount</code> value. If you haven't dropped this idea yet, this should be enough but for dummies, this means that whenever you update the <code style="color:#e2026e">counter</code> state in one instance, it'll update the <code style="color:#707f01">prevCount</code> variable for ALL components.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">On the surface, this approach solves our problem but if you look deeper we have ended up with more problems than before. Is there a way to do all this without all this fuss?</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">React <code style="color:#13990c">useRef</code> hook</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">From the React docs:</p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><code style="color:#13990c">useRef</code> returns a mutable ref object whose <code style="color:#d30090">current</code> property is initialized to the passed argument (<code style="color:#5d7001">initialValue</code>). The returned object will persist for the full lifetime of the component.</p>
</blockquote>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In English, this means you can assign any value to <code style="color:#d30090">current</code> property of <code style="color:#13990c">useRef</code> hook and update it without causing a re-render.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Reusing our Counter example, let's try to store the <code style="color:#707f01">prevCount</code> in a useRef hook:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> prevCount <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You'll also have to update your increment logic &amp; your <code style="color:#31ad0f">useEffect</code> hook. The final code would look like this:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-jsx"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">Counter</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token keyword">const</span> prevCount <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>  <span class="token keyword">const</span> <span class="token punctuation">[</span>counter<span class="token punctuation">,</span> setCounter<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span>  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span>    <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'counter:'</span><span class="token punctuation">,</span> counter<span class="token punctuation">,</span> <span class="token string">'prevCount:'</span><span class="token punctuation">,</span> prevCount<span class="token punctuation">.</span><span class="token property-access">current</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>prevCount<span class="token punctuation">,</span> counter<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>
<span class="ln-num" data-num="8"></span>  <span class="token keyword control-flow">return</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="9"></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>App<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="10"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>counter<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="11"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span>
<span class="ln-num" data-num="12"></span>        <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="13"></span>          <span class="token function">setCounter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">counter</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="14"></span>            prevCount<span class="token punctuation">.</span><span class="token property-access">current</span> <span class="token operator">=</span> counter<span class="token punctuation">;</span> <span class="token comment">// store previous value in prevCount</span>
<span class="ln-num" data-num="15"></span>            <span class="token keyword control-flow">return</span> counter <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// increment</span>
<span class="ln-num" data-num="16"></span>          <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="17"></span>        <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="18"></span>      <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="19"></span>        Increment counter
<span class="ln-num" data-num="20"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="21"></span>    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="22"></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="23"></span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Does it work?</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/counter_3.33JTMZGM.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/counter_3.CmOLkHJ2.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/counter_3.CmOLkHJ2.png" srcset="https://blog.notesnook.com/assets/static/counter_3.CmOLkHJ2.png 640w" sizes="100vw" alt="react useref hook counter example" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Hell yeah it works! And you get multiple benefits:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Our <code style="color:#707f01">prevCount</code> value is persisted across re-renders.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We can update it easily by setting new value to <code style="color:#ea1088">.current</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The value will automatically reset when our component unmounts.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We can reuse our <code style="color:#890906">Counter</code> component without any issue since each component will have it's out <code style="color:#707f01">prevCount</code> ref.</li>
</ol>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Common use cases of React <code style="color:#13990c">useRef</code> hook</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Obviously, the above example is a little too simple. In real world use case, you'd probably not use useRef like that. So how <em>would</em> you use it?</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Accessing DOM elements directly</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">React was specifically made to avoid this. Direct DOM access is a bad idea for many reasons. There are very few cases where you can't use React state to update a value or a style. Despite that, React <code style="color:#13990c">useRef</code> can be useful --- especially if you want to use the DOM functions of an element like <code style="color:#e2026b">focus</code>, <code style="color:#c10158">blur</code> etc.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Suppose you wanted to focus an <code style="color:#ed0e81">input</code> element when the counter reaches 10. You could easily do that with <code style="color:#13990c">useRef</code> like this:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-jsx"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">Counter</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token keyword">const</span> inputRef <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>  <span class="token keyword">const</span> <span class="token punctuation">[</span>counter<span class="token punctuation">,</span> setCounter<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span>
<span class="ln-num" data-num="5"></span>  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="6"></span>    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>counter <span class="token operator">===</span> <span class="token number">10</span> <span class="token operator">&amp;&amp;</span> inputRef<span class="token punctuation">.</span><span class="token property-access">current</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="7"></span>      inputRef<span class="token punctuation">.</span><span class="token property-access">current</span><span class="token punctuation">.</span><span class="token method function property-access">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="9"></span>  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>counter<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="10"></span>
<span class="ln-num" data-num="11"></span>  <span class="token keyword control-flow">return</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="12"></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>App<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="13"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>counter<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="14"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span>
<span class="ln-num" data-num="15"></span>        <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="16"></span>          <span class="token function">setCounter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">counter</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="17"></span>            <span class="token keyword control-flow">return</span> counter <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// increment</span>
<span class="ln-num" data-num="18"></span>          <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="19"></span>        <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="ln-num" data-num="20"></span>      <span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="21"></span>        Increment counter
<span class="ln-num" data-num="22"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="23"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span> <span class="token punctuation">/&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="24"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span> <span class="token punctuation">/&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="25"></span>      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>inputRef<span class="token punctuation">}</span></span> <span class="token punctuation">/&gt;</span></span><span class="token plain-text">
<span class="ln-num" data-num="26"></span>    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="ln-num" data-num="27"></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="28"></span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Conclusion</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">React <code style="color:#13990c">useRef</code> hook is much more "useful" than you might think at first. One thing to note here is that it is just a Javascript object --- it can store anything that you need to update, and keep track of, without causing a re-render.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">However, this doesn't mean you ditch <code style="color:#297507">useState</code> for <code style="color:#13990c">useRef</code>. Each has its uses. React is not a slow web component framework and it is carefully built to account for many performance issues. Although useState seems like a huge step back from "just update the value", it keeps things very predictable --- single definition, single way to update it, single way to keep track of updates.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">On the other hand, <code style="color:#13990c">useRef</code> is useful when you need to access functions of a component or "secretly" change the style of a component without a re-render.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In the next articles, I'll discuss the most common ways React <code style="color:#13990c">useRef</code> hook is misused. Till then, have a good day!</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">References</h3>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://reactjs.org/docs/hooks-reference.html" target="_blank" class="c_accent">Hooks API reference</a> React Docs</li>
</ul>]]></content:encoded>
            <author>Ammar Ahmed</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.C9-wFblM.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[React Native JSI: Part 2 - Converting Native Modules to JSI Modules]]></title>
            <link>https://blog.notesnook.com/convert-native-modules-to-react-native-jsi-modules</link>
            <guid isPermaLink="false">https://blog.notesnook.com/convert-native-modules-to-react-native-jsi-modules</guid>
            <pubDate>Wed, 11 Aug 2021 12:01:22 GMT</pubDate>
            <description><![CDATA[React Native JSI seems daunting but JSI is, by far, the best way to deliver native performance. And the best part? No overhead. No unnecessary Promises.]]></description>
            <content:encoded><![CDATA[<div class="css-xmhgif"><p style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you don't know what JSI is or are confused about it, I recommend that you
read <a href="https://blog.notesnook.com/getting-started-react-native-jsi/" target="_blank" class="c_accent">part 1 of this
series</a> before
continuing.</p><p></p></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In my <a href="https://blog.notesnook.com/getting-started-react-native-jsi/" target="_blank" class="c_accent">previous blog</a>
I explained in detail how you can write JSI Modules in React Native from scratch.
I talked about the basics and then explained how to write functions in C++ which
you can then call in React Native.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But we all know that most of the native modules in React Native are written in Java or Objective C. While some of them can be rewritten in C++, most of these native modules use platform specific APIs and SDKs and it's just not possible to write them in C++.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In this post, I will be discussing how we can convert these Native Modules to React Native JSI modules. I won't touch any of the basics in this post. I have already explained them in <a href="https://blog.notesnook.com/getting-started-react-native-jsi/" target="_blank" class="c_accent">the previous part of this series</a>. If you don't know what JSI is or are still confused about it, I recommend that you read it before continuing.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/jsi-image.BOnK09o0.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/jsi-image.B2CZr4Xa.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/jsi-image.B2CZr4Xa.png" srcset="https://blog.notesnook.com/assets/static/jsi-image.B2CZr4Xa.png 640w" sizes="100vw" alt="Communication between Javascript and Android/iOS environment with JSI" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Communication between Javascript and Android/iOS environment with JSI</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The above figure details how this communication works. There are no signs of a React Native Bridge. We are using C++ as a middleware for two way communication between platform specific code and Javascript Runtime.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">On iOS this process is very simple because C++ code can run directly alongside Objective C code.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">On Android it is a bit complicated because communication between Android and C++ happens through JNI (Java Native Interface). But once you have the basic setup done, the rest is just wrapper code.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I will be reusing the <a href="https://github.com/ammarahm-ed/react-native-simple-jsi" target="_blank" class="c_accent">react-native-simple-jsi</a> example library to implement a basic communication layer between Javascript Runtime and, iOS and Android as detailed in the diagram above. I suggest you <code style="color:#e014e0">git clone</code> the repo so you can follow along.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Android</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Open the <code style="color:#db0ab5">example/android</code> folder in Android Studio. Once Gradle build is complete, navigate to <code style="color:#9906ce">react-native-simple-jsi -&gt; java -&gt; SimpleJsiModule.java</code>.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/androidstudio-1.CLoevqZz.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/androidstudio-1.BjHn1CUd.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/androidstudio-1.BjHn1CUd.png" srcset="https://blog.notesnook.com/assets/static/androidstudio-1.BjHn1CUd.png 640w" sizes="100vw" alt="JSI module on android java code" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2"><code style="color:#d114a2">SimpleJsiModule.java</code></h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Let's add a simple function that retrieves our Android device model:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-java"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getModel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span><span class="token class-name">String</span> manufacturer <span class="token operator">=</span> <span class="token class-name">Build</span><span class="token punctuation">.</span><span class="token constant">MANUFACTURER</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span><span class="token class-name">String</span> model <span class="token operator">=</span> <span class="token class-name">Build</span><span class="token punctuation">.</span><span class="token constant">MODEL</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span><span class="token keyword">if</span> <span class="token punctuation">(</span>model<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span>manufacturer<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span><span class="token keyword">return</span> model<span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span><span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="7"></span><span class="token keyword">return</span> manufacturer <span class="token operator">+</span> <span class="token string">" "</span> <span class="token operator">+</span> model<span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span><span class="token punctuation">}</span>
<span class="ln-num" data-num="9"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Let's also add two functions to read and write data to Shared Prefrences on Android:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-java"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setItem</span><span class="token punctuation">(</span><span class="token keyword">final</span> <span class="token class-name">String</span> key<span class="token punctuation">,</span> <span class="token keyword">final</span> <span class="token class-name">String</span> value<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span><span class="token class-name">SharedPreferences</span> preferences <span class="token operator">=</span> <span class="token class-name">PreferenceManager</span><span class="token punctuation">.</span><span class="token function">getDefaultSharedPreferences</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getReactApplicationContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span><span class="token class-name">SharedPreferences<span class="token punctuation">.</span>Editor</span> editor <span class="token operator">=</span> preferences<span class="token punctuation">.</span><span class="token function">edit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span>editor<span class="token punctuation">.</span><span class="token function">putString</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span>editor<span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span><span class="token punctuation">}</span>
<span class="ln-num" data-num="7"></span>
<span class="ln-num" data-num="8"></span><span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getItem</span><span class="token punctuation">(</span><span class="token keyword">final</span> <span class="token class-name">String</span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="9"></span><span class="token class-name">SharedPreferences</span> preferences <span class="token operator">=</span> <span class="token class-name">PreferenceManager</span><span class="token punctuation">.</span><span class="token function">getDefaultSharedPreferences</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getReactApplicationContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="10"></span><span class="token class-name">String</span> value <span class="token operator">=</span> preferences<span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span><span class="token keyword">return</span> value<span class="token punctuation">;</span>
<span class="ln-num" data-num="12"></span><span class="token punctuation">}</span></code></pre></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2"><code style="color:#db06b4">cpp-adapter.cpp</code></h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To call any of the above functions from C++ we need access to the Java environment and the current instance of the <code style="color:#a80a14">SimpleJsiModule</code> class.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A naive approach will be reusing the <code style="color:#e20eed">JNIEnv*</code> we already have:</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi)</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">However caching a <code style="color:#e20eed">JNIEnv*</code> is not a good idea because you can't use the same <code style="color:#e20eed">JNIEnv*</code> across multiple threads, and might not even be able to use it for multiple native calls on the same thread (see <a href="https://android-developers.blogspot.se/2011/11/jni-local-reference-changes-in-ics.html" target="_blank" class="c_accent">http://android-developers.blogspot.se/2011/11/jni-local-reference-changes-in-ics.html</a>).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A better implementation would be to create a function that helps us retrieves <code style="color:#e20eed">JNIEnv*</code> whenever and wherever we need it:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-cpp"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;jni.h&gt;</span></span>
<span class="ln-num" data-num="2"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;sys/types.h&gt;</span></span>
<span class="ln-num" data-num="3"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"example.h"</span></span>
<span class="ln-num" data-num="4"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"pthread.h"</span></span>
<span class="ln-num" data-num="5"></span>
<span class="ln-num" data-num="6"></span>JavaVM <span class="token operator">*</span>java_vm<span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>jclass java_class<span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span>jobject java_object<span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span>
<span class="ln-num" data-num="10"></span><span class="token keyword">extern</span> <span class="token string">"C"</span> JNIEXPORT <span class="token keyword">void</span> JNICALL
<span class="ln-num" data-num="11"></span><span class="token function">Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall</span><span class="token punctuation">(</span>JNIEnv <span class="token operator">*</span>env<span class="token punctuation">,</span>
<span class="ln-num" data-num="12"></span>                                                            jobject thiz<span class="token punctuation">,</span>
<span class="ln-num" data-num="13"></span>                                                            jlong jsi<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="14"></span>
<span class="ln-num" data-num="15"></span>  <span class="token keyword">auto</span> runtime <span class="token operator">=</span> <span class="token generic-function"><span class="token function">reinterpret_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>facebook<span class="token double-colon punctuation">::</span>jsi<span class="token double-colon punctuation">::</span>Runtime <span class="token operator">*</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>jsi<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="16"></span>
<span class="ln-num" data-num="17"></span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>runtime<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="18"></span>    example<span class="token double-colon punctuation">::</span><span class="token function">install</span><span class="token punctuation">(</span><span class="token operator">*</span>runtime<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="19"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="20"></span>
<span class="ln-num" data-num="21"></span>  env<span class="token operator">-&gt;</span><span class="token function">GetJavaVM</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>java_vm<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="22"></span>  java_object <span class="token operator">=</span> env<span class="token operator">-&gt;</span><span class="token function">NewGlobalRef</span><span class="token punctuation">(</span>thiz<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="23"></span><span class="token punctuation">}</span>
<span class="ln-num" data-num="24"></span>
<span class="ln-num" data-num="25"></span><span class="token comment">/**
<span class="ln-num" data-num="26"></span>* A simple callback function that allows us to detach current JNI Environment
<span class="ln-num" data-num="27"></span>* when the thread
<span class="ln-num" data-num="28"></span>* See https://stackoverflow.com/a/30026231 for detailed explanation
<span class="ln-num" data-num="29"></span>*/</span>
<span class="ln-num" data-num="30"></span>
<span class="ln-num" data-num="31"></span><span class="token keyword">void</span> <span class="token function">DeferThreadDetach</span><span class="token punctuation">(</span>JNIEnv <span class="token operator">*</span>env<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="32"></span>  <span class="token keyword">static</span> pthread_key_t thread_key<span class="token punctuation">;</span>
<span class="ln-num" data-num="33"></span>
<span class="ln-num" data-num="34"></span>  <span class="token comment">// Set up a Thread Specific Data key, and a callback that</span>
<span class="ln-num" data-num="35"></span>  <span class="token comment">// will be executed when a thread is destroyed.</span>
<span class="ln-num" data-num="36"></span>  <span class="token comment">// This is only done once, across all threads, and the value</span>
<span class="ln-num" data-num="37"></span>  <span class="token comment">// associated with the key for any given thread will initially</span>
<span class="ln-num" data-num="38"></span>  <span class="token comment">// be NULL.</span>
<span class="ln-num" data-num="39"></span>  <span class="token keyword">static</span> <span class="token keyword">auto</span> run_once <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="40"></span>    <span class="token keyword">const</span> <span class="token keyword">auto</span> err <span class="token operator">=</span> <span class="token function">pthread_key_create</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>thread_key<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token keyword">void</span> <span class="token operator">*</span>ts_env<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="41"></span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>ts_env<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="42"></span>        java_vm<span class="token operator">-&gt;</span><span class="token function">DetachCurrentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="43"></span>      <span class="token punctuation">}</span>
<span class="ln-num" data-num="44"></span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="45"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="46"></span>      <span class="token comment">// Failed to create TSD key. Throw an exception if you want to.</span>
<span class="ln-num" data-num="47"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="48"></span>    <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="49"></span>  <span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="50"></span>
<span class="ln-num" data-num="51"></span>  <span class="token comment">// For the callback to actually be executed when a thread exits</span>
<span class="ln-num" data-num="52"></span>  <span class="token comment">// we need to associate a non-NULL value with the key on that thread.</span>
<span class="ln-num" data-num="53"></span>  <span class="token comment">// We can use the JNIEnv* as that value.</span>
<span class="ln-num" data-num="54"></span>  <span class="token keyword">const</span> <span class="token keyword">auto</span> ts_env <span class="token operator">=</span> <span class="token function">pthread_getspecific</span><span class="token punctuation">(</span>thread_key<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="55"></span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ts_env<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="56"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">pthread_setspecific</span><span class="token punctuation">(</span>thread_key<span class="token punctuation">,</span> env<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="57"></span>      <span class="token comment">// Failed to set thread-specific value for key. Throw an exception if you</span>
<span class="ln-num" data-num="58"></span>      <span class="token comment">// want to.</span>
<span class="ln-num" data-num="59"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="60"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="61"></span><span class="token punctuation">}</span>
<span class="ln-num" data-num="62"></span>
<span class="ln-num" data-num="63"></span><span class="token comment">/**
<span class="ln-num" data-num="64"></span>* Get a JNIEnv* valid for this thread, regardless of whether
<span class="ln-num" data-num="65"></span>* we're on a native thread or a Java thread.
<span class="ln-num" data-num="66"></span>* If the calling thread is not currently attached to the JVM
<span class="ln-num" data-num="67"></span>* it will be attached, and then automatically detached when the
<span class="ln-num" data-num="68"></span>* thread is destroyed.
<span class="ln-num" data-num="69"></span>*
<span class="ln-num" data-num="70"></span>* See https://stackoverflow.com/a/30026231 for detailed explanation
<span class="ln-num" data-num="71"></span>*/</span>
<span class="ln-num" data-num="72"></span>JNIEnv <span class="token operator">*</span><span class="token function">GetJniEnv</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="73"></span>  JNIEnv <span class="token operator">*</span>env <span class="token operator">=</span> <span class="token keyword">nullptr</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="74"></span>  <span class="token comment">// We still call GetEnv first to detect if the thread already</span>
<span class="ln-num" data-num="75"></span>  <span class="token comment">// is attached. This is done to avoid setting up a DetachCurrentThread</span>
<span class="ln-num" data-num="76"></span>  <span class="token comment">// call on a Java thread.</span>
<span class="ln-num" data-num="77"></span>
<span class="ln-num" data-num="78"></span>  <span class="token comment">// g_vm is a global.</span>
<span class="ln-num" data-num="79"></span>  <span class="token keyword">auto</span> get_env_result <span class="token operator">=</span> java_vm<span class="token operator">-&gt;</span><span class="token function">GetEnv</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">void</span> <span class="token operator">*</span><span class="token operator">*</span><span class="token punctuation">)</span><span class="token operator">&amp;</span>env<span class="token punctuation">,</span> JNI_VERSION_1_6<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="80"></span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>get_env_result <span class="token operator">==</span> JNI_EDETACHED<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="81"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>java_vm<span class="token operator">-&gt;</span><span class="token function">AttachCurrentThread</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>env<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token operator">==</span> JNI_OK<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="82"></span>      <span class="token function">DeferThreadDetach</span><span class="token punctuation">(</span>env<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="83"></span>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="84"></span>      <span class="token comment">// Failed to attach thread. Throw an exception if you want to.</span>
<span class="ln-num" data-num="85"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="86"></span>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>get_env_result <span class="token operator">==</span> JNI_EVERSION<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="87"></span>    <span class="token comment">// Unsupported JNI version. Throw an exception if you want to.</span>
<span class="ln-num" data-num="88"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="89"></span>  <span class="token keyword">return</span> env<span class="token punctuation">;</span>
<span class="ln-num" data-num="90"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Let's discuss what the above code actually does:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">At the top of the file, we are defining three global vars.<!-- -->
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#0a0a0a">java_vm</code> A global reference of our Java Runtime in which our android app is running. We need this get access JNI environment</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#282828">java_class</code> Java side <strong>class method</strong> (the native methods declared <strong>with</strong> "<strong>static</strong>"). <code style="color:#ce06ba">jclass</code> is a reference to the <em>current class</em>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#0a0a0a">java_object</code> Java side <strong>instance method</strong> (the native methods declared <strong>without</strong> "<strong>static</strong>"). <code style="color:#db17ed">jobject</code> is a reference to the <em>current instance</em>.</li>
</ol>
</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">In our <code style="color:#378206">nativeInstall</code> method we are getting the current Java VM from JNI Environement and keeping its reference in <code style="color:#0a0a0a">java_vm</code>. We are also storing current instance of <code style="color:#a80a14">SimpleJsiModule</code> class in a GlobalRef.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Finally we have <code style="color:#cc06e2">GetJniEnv()</code> method which we will be using to get the JNI environment whenever we need it.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Since <code style="color:#bf1170">example::install</code> includes functions that are platform agnostic, let's create another <code style="color:#ca0eef">install</code> function inside the <code style="color:#db06b4">cpp-adapter.cpp</code> file which will install Android specific functions:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-cpp"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">void</span> <span class="token function">install</span><span class="token punctuation">(</span>facebook<span class="token double-colon punctuation">::</span>jsi<span class="token double-colon punctuation">::</span>Runtime <span class="token operator">&amp;</span>jsiRuntime<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span>  <span class="token keyword">auto</span> getDeviceName <span class="token operator">=</span> <span class="token class-name">Function</span><span class="token double-colon punctuation">::</span><span class="token function">createFromHostFunction</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="4"></span>      jsiRuntime<span class="token punctuation">,</span> <span class="token class-name">PropNameID</span><span class="token double-colon punctuation">::</span><span class="token function">forAscii</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"getDeviceName"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="5"></span>      <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">(</span>Runtime <span class="token operator">&amp;</span>runtime<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">&amp;</span>thisValue<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">*</span>arguments<span class="token punctuation">,</span>
<span class="ln-num" data-num="6"></span>         size_t count<span class="token punctuation">)</span> <span class="token operator">-&gt;</span> Value <span class="token punctuation">{</span>
<span class="ln-num" data-num="7"></span>
<span class="ln-num" data-num="8"></span>        JNIEnv <span class="token operator">*</span>jniEnv <span class="token operator">=</span> <span class="token function">GetJniEnv</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span>
<span class="ln-num" data-num="10"></span>        java_class <span class="token operator">=</span> jniEnv<span class="token operator">-&gt;</span><span class="token function">GetObjectClass</span><span class="token punctuation">(</span>java_object<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span>        jmethodID getModel <span class="token operator">=</span>
<span class="ln-num" data-num="12"></span>            jniEnv<span class="token operator">-&gt;</span><span class="token function">GetMethodID</span><span class="token punctuation">(</span>java_class<span class="token punctuation">,</span> <span class="token string">"getModel"</span><span class="token punctuation">,</span> <span class="token string">"()Ljava/lang/String;"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>        jobject result <span class="token operator">=</span> jniEnv<span class="token operator">-&gt;</span><span class="token function">CallObjectMethod</span><span class="token punctuation">(</span>java_object<span class="token punctuation">,</span> getModel<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="14"></span>        <span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>str <span class="token operator">=</span> jniEnv<span class="token operator">-&gt;</span><span class="token function">GetStringUTFChars</span><span class="token punctuation">(</span><span class="token punctuation">(</span>jstring<span class="token punctuation">)</span>result<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>
<span class="ln-num" data-num="16"></span>        <span class="token keyword">return</span> <span class="token function">Value</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> <span class="token class-name">String</span><span class="token double-colon punctuation">::</span><span class="token function">createFromUtf8</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> str<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="17"></span>
<span class="ln-num" data-num="18"></span>      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="19"></span>
<span class="ln-num" data-num="20"></span>  jsiRuntime<span class="token punctuation">.</span><span class="token function">global</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"getDeviceName"</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="21"></span>                                  <span class="token function">move</span><span class="token punctuation">(</span>getDeviceName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="22"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are installing the <code style="color:#188400">getDeviceName</code> function which calls <code style="color:#099610">getModel</code> function which we created in the beginning.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#c61181">GetJniEnv();</code> Gives us the current attached JNI environment with the Java VM</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#d80fce">GetObjectClass(java_object);</code> Retrieve the Java Class which includes all the methods we need to call</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ea00a8">GetMethodID(java_class, "getModel", "()Ljava/lang/String;");</code> Get the method ID for the function we want to call in Java. In our case it is getModel. This third argument is automatically generated in Android Studio when you select the correct method in the second argument from intellisense.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#a00111">CallObjectMethod</code> Call the method with the ID we got above.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#a0091d">GetStringUTFChars</code> The method returns a jobject from which we need to get UTF8 chars.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Finally we return our value to Javascript.</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2"><code style="color:#a410d1">App.js</code></h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Now all we have to do in Javascript is call <code style="color:#c90cb9">global.getDeviceName</code> and we get our value.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">console.log(global.getDeviceName);</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I ran this in the Android Emulator and got <code style="color:#e509de">Google skd_gphone_x86</code> in 0.03ms.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/android-phone.CxAHJgw5.webp 384w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/android-phone.Bfk_HvK3.png 384w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/android-phone.Bfk_HvK3.png" srcset="https://blog.notesnook.com/assets/static/android-phone.Bfk_HvK3.png 384w" sizes="100vw" alt="JSI module on android java code" style="max-width:384px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But what about the two other methods (<code style="color:#029126">setItem</code> and <code style="color:#0ba399">getItem</code>) I added in the beginning? Let's add JSI bindings for them too:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-cpp"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">auto</span> setItem <span class="token operator">=</span> <span class="token class-name">Function</span><span class="token double-colon punctuation">::</span><span class="token function">createFromHostFunction</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="2"></span>jsiRuntime<span class="token punctuation">,</span> <span class="token class-name">PropNameID</span><span class="token double-colon punctuation">::</span><span class="token function">forAscii</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"setItem"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">(</span>Runtime <span class="token operator">&amp;</span>runtime<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">&amp;</span>thisValue<span class="token punctuation">,</span> <span class="token keyword">const</span> Value \<span class="token operator">*</span>arguments<span class="token punctuation">,</span>
<span class="ln-num" data-num="4"></span>size_t count<span class="token punctuation">)</span> <span class="token operator">-&gt;</span> Value <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span>
<span class="ln-num" data-num="6"></span>      string key <span class="token operator">=</span> arguments<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span>runtime<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">utf8</span><span class="token punctuation">(</span>runtime<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>      string value <span class="token operator">=</span> arguments<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span>runtime<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">utf8</span><span class="token punctuation">(</span>runtime<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span>
<span class="ln-num" data-num="9"></span>      JNIEnv <span class="token operator">*</span>jniEnv <span class="token operator">=</span> <span class="token function">GetJniEnv</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="10"></span>
<span class="ln-num" data-num="11"></span>      java_class <span class="token operator">=</span> jniEnv<span class="token operator">-&gt;</span><span class="token function">GetObjectClass</span><span class="token punctuation">(</span>java_object<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="12"></span>      jmethodID set <span class="token operator">=</span> jniEnv<span class="token operator">-&gt;</span><span class="token function">GetMethodID</span><span class="token punctuation">(</span>           java_class<span class="token punctuation">,</span> <span class="token string">"setItem"</span><span class="token punctuation">,</span> <span class="token string">"(Ljava/lang/String;Ljava/lang/String;)V"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>
<span class="ln-num" data-num="14"></span>      jstring jstr1 <span class="token operator">=</span> <span class="token function">string2jstring</span><span class="token punctuation">(</span>jniEnv<span class="token punctuation">,</span> key<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>      jstring jstr2 <span class="token operator">=</span> <span class="token function">string2jstring</span><span class="token punctuation">(</span>jniEnv<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="16"></span>      jvalue params<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="17"></span>      params<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>l <span class="token operator">=</span> jstr1<span class="token punctuation">;</span>       params<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>l <span class="token operator">=</span> jstr2<span class="token punctuation">;</span>
<span class="ln-num" data-num="18"></span>      jniEnv<span class="token operator">-&gt;</span><span class="token function">CallVoidMethodA</span><span class="token punctuation">(</span>java_object<span class="token punctuation">,</span> set<span class="token punctuation">,</span> params<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="19"></span>
<span class="ln-num" data-num="20"></span>      <span class="token keyword">return</span> <span class="token function">Value</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="21"></span>
<span class="ln-num" data-num="22"></span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="23"></span>
<span class="ln-num" data-num="24"></span>jsiRuntime<span class="token punctuation">.</span><span class="token function">global</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"setItem"</span><span class="token punctuation">,</span> <span class="token function">move</span><span class="token punctuation">(</span>setItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The <code style="color:#029126">setItem</code> function has 2 arguments, <code style="color:#b113d8">key</code> and <code style="color:#cf00d6">value</code> respectively.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#608205">paramsCount</code> is set to 2</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#b614e2">GetMethodID(java_class, "setItem","(Ljava/lang/String;Ljava/lang/String;)V");</code> Get method ID for setItem function in Java.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ed07bf">string2jstring</code> A helper function to convert <code style="color:#af0a4c">std::string</code> to java <code style="color:#a0100b">String</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#c107af">jvalue params[2];</code> Initializing a <code style="color:#c10da0">jvalue</code> with two parameters since our <code style="color:#029126">setItem</code> function has two arguments.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#910d15">CallVoidMethodA</code> Since our method does not return a value and has arguments.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Finally we return a <code style="color:#bd03c6">boolean</code> value to Javascript</li>
</ol>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-cpp"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">auto</span> getItem <span class="token operator">=</span> <span class="token class-name">Function</span><span class="token double-colon punctuation">::</span><span class="token function">createFromHostFunction</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="2"></span>    jsiRuntime<span class="token punctuation">,</span> <span class="token class-name">PropNameID</span><span class="token double-colon punctuation">::</span><span class="token function">forAscii</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"getItem"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span>    <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">(</span>Runtime <span class="token operator">&amp;</span>runtime<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">&amp;</span>thisValue<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">*</span>arguments<span class="token punctuation">,</span>
<span class="ln-num" data-num="4"></span>       size_t count<span class="token punctuation">)</span> <span class="token operator">-&gt;</span> Value <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span>
<span class="ln-num" data-num="6"></span>      string key <span class="token operator">=</span> arguments<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span>runtime<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">utf8</span><span class="token punctuation">(</span>runtime<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>
<span class="ln-num" data-num="8"></span>      JNIEnv <span class="token operator">*</span>jniEnv <span class="token operator">=</span> <span class="token function">GetJniEnv</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span>
<span class="ln-num" data-num="10"></span>      java_class <span class="token operator">=</span> jniEnv<span class="token operator">-&gt;</span><span class="token function">GetObjectClass</span><span class="token punctuation">(</span>java_object<span class="token punctuation">)</span><span class="token punctuation">;</span>       jmethodID get <span class="token operator">=</span> jniEnv<span class="token operator">-&gt;</span><span class="token function">GetMethodID</span><span class="token punctuation">(</span>           java_class<span class="token punctuation">,</span> <span class="token string">"getItem"</span><span class="token punctuation">,</span> <span class="token string">"(Ljava/lang/String;)Ljava/lang/String;"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span>
<span class="ln-num" data-num="12"></span>      jstring jstr1 <span class="token operator">=</span> <span class="token function">string2jstring</span><span class="token punctuation">(</span>jniEnv<span class="token punctuation">,</span> key<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>      jvalue params<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="14"></span>      params<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>l <span class="token operator">=</span> jstr1<span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>      jobject result <span class="token operator">=</span> jniEnv<span class="token operator">-&gt;</span><span class="token function">CallObjectMethodA</span><span class="token punctuation">(</span>java_object<span class="token punctuation">,</span> get<span class="token punctuation">,</span> params<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="16"></span>      <span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>str <span class="token operator">=</span> jniEnv<span class="token operator">-&gt;</span><span class="token function">GetStringUTFChars</span><span class="token punctuation">(</span><span class="token punctuation">(</span>jstring<span class="token punctuation">)</span>result<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="17"></span>
<span class="ln-num" data-num="18"></span>      <span class="token keyword">return</span> <span class="token function">Value</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> <span class="token class-name">String</span><span class="token double-colon punctuation">::</span><span class="token function">createFromUtf8</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> str<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="19"></span>
<span class="ln-num" data-num="20"></span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="21"></span>
<span class="ln-num" data-num="22"></span>jsiRuntime<span class="token punctuation">.</span><span class="token function">global</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"getItem"</span><span class="token punctuation">,</span> <span class="token function">move</span><span class="token punctuation">(</span>getItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The <code style="color:#0ba399">getItem</code> function has 1 argument, key and returns a string value.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#608205">paramsCount</code> is set to 1</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#bf038a">GetMethodID(java_class, "getItem","(Ljava/lang/String;)Ljava/lang/String;");</code> Get method ID for <code style="color:#0ba399">getItem</code> function in Java.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ed07bf">string2jstring</code> A helper function to convert <code style="color:#af0a4c">std::string</code> to java <code style="color:#a0100b">String</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#e506cf">jvalue params[1];</code> Initializing a <code style="color:#c10da0">jvalue</code> with one parameter since our <code style="color:#029126">setItem</code> function has one argument.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#a5042f">CallObjectMethodA</code> Since our method returns a String value and has arguments.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Finally we return the <code style="color:#a0100b">String</code> value to Javascript</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And that's it. At least for the Android part. Now let's see how we can do the same on iOS.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">iOS</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Let's open <code style="color:#cc026d">example/ios/example.xcworkspace</code> in XCode. Navigate to <code style="color:#b611bf">Pods &gt; Development Pods &gt; react-native-simple-jsi -&gt; ios</code> and open <code style="color:#c60d70">SimpleJsi.mm</code>.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/xcode-1.C5mcC5Pu.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/xcode-1.DAxM3Z-0.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/xcode-1.DAxM3Z-0.png" srcset="https://blog.notesnook.com/assets/static/xcode-1.DAxM3Z-0.png 640w" sizes="100vw" alt="Xcode image of JSI module" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Similar to Android, we will create three functions, <code style="color:#099610">getModel</code>, <code style="color:#029126">setItem</code> and <code style="color:#0ba399">getItem</code>.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-objc"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token operator">-</span> <span class="token punctuation">(</span>NSString <span class="token operator">*</span><span class="token punctuation">)</span>getModel <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span>  <span class="token keyword">struct</span> utsname systemInfo<span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span>
<span class="ln-num" data-num="5"></span>  <span class="token function">uname</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>systemInfo<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>
<span class="ln-num" data-num="7"></span>  <span class="token keyword">return</span> <span class="token punctuation">[</span>NSString stringWithCString<span class="token punctuation">:</span>systemInfo<span class="token punctuation">.</span>machine
<span class="ln-num" data-num="8"></span>                            encoding<span class="token punctuation">:</span>NSUTF8StringEncoding<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span><span class="token punctuation">}</span>
<span class="ln-num" data-num="10"></span>
<span class="ln-num" data-num="11"></span><span class="token operator">-</span> <span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span>setItem<span class="token punctuation">:</span><span class="token punctuation">(</span>NSString <span class="token operator">*</span><span class="token punctuation">)</span>key<span class="token punctuation">:</span><span class="token punctuation">(</span>NSString <span class="token operator">*</span><span class="token punctuation">)</span>value <span class="token punctuation">{</span>
<span class="ln-num" data-num="12"></span>
<span class="ln-num" data-num="13"></span>  NSUserDefaults <span class="token operator">*</span>standardUserDefaults <span class="token operator">=</span> <span class="token punctuation">[</span>NSUserDefaults standardUserDefaults<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="14"></span>
<span class="ln-num" data-num="15"></span>  <span class="token punctuation">[</span>standardUserDefaults setObject<span class="token punctuation">:</span>value forKey<span class="token punctuation">:</span>key<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="16"></span>
<span class="ln-num" data-num="17"></span>  <span class="token punctuation">[</span>standardUserDefaults synchronize<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="18"></span><span class="token punctuation">}</span>
<span class="ln-num" data-num="19"></span>
<span class="ln-num" data-num="20"></span><span class="token operator">-</span> <span class="token punctuation">(</span>NSString <span class="token operator">*</span><span class="token punctuation">)</span>getItem<span class="token punctuation">:</span><span class="token punctuation">(</span>NSString <span class="token operator">*</span><span class="token punctuation">)</span>key <span class="token punctuation">{</span>
<span class="ln-num" data-num="21"></span>
<span class="ln-num" data-num="22"></span>  NSUserDefaults <span class="token operator">*</span>standardUserDefaults <span class="token operator">=</span> <span class="token punctuation">[</span>NSUserDefaults standardUserDefaults<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="23"></span>
<span class="ln-num" data-num="24"></span>  <span class="token keyword">return</span> <span class="token punctuation">[</span>standardUserDefaults stringForKey<span class="token punctuation">:</span>key<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="25"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">All these methods return <code style="color:#b2120c">NSString</code> values but these are not directly readable in JSI. Let's convert JSI values to NS values and NS values to JSI values using the <a href="https://gist.github.com/Jarred-Sumner/ae255ddf70e9df9a1e0c766e113a59c6" target="_blank" class="c_accent">awesome <code style="color:#9b2c0a">YeetJSIUtils</code> module</a>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">All you have to do is add the two files (<code style="color:#e806cd">YeetJSIUtils.mm</code> &amp; <code style="color:#e512bb">YeetJSIUtils.h</code>) to the project and import them in <code style="color:#c60d70">SimpleJsi.mm</code> file to call the required helper functions.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Finally let's install the host functions in JSI like we did in the Android part above:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-objc"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">install</span><span class="token punctuation">(</span>jsi<span class="token punctuation">:</span><span class="token punctuation">:</span>Runtime <span class="token operator">&amp;</span>jsiRuntime<span class="token punctuation">,</span> SimpleJsi \<span class="token operator">*</span>simpleJsi<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span><span class="token keyword">auto</span> getDeviceName <span class="token operator">=</span> Function<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">createFromHostFunction</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="4"></span>jsiRuntime<span class="token punctuation">,</span> PropNameID<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">forAscii</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"getDeviceName"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="5"></span><span class="token punctuation">[</span>simpleJsi<span class="token punctuation">]</span><span class="token punctuation">(</span>Runtime <span class="token operator">&amp;</span>runtime<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">&amp;</span>thisValue<span class="token punctuation">,</span>
<span class="ln-num" data-num="6"></span><span class="token keyword">const</span> Value \<span class="token operator">*</span>arguments<span class="token punctuation">,</span> size_t count<span class="token punctuation">)</span> <span class="token operator">-&gt;</span> Value <span class="token punctuation">{</span>
<span class="ln-num" data-num="7"></span>
<span class="ln-num" data-num="8"></span>        jsi<span class="token punctuation">:</span><span class="token punctuation">:</span>String deviceName <span class="token operator">=</span>
<span class="ln-num" data-num="9"></span>            <span class="token function">convertNSStringToJSIString</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> <span class="token punctuation">[</span>simpleJsi getModel<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="10"></span>
<span class="ln-num" data-num="11"></span>        <span class="token keyword">return</span> <span class="token function">Value</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> deviceName<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="12"></span>      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>
<span class="ln-num" data-num="14"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The first thing to notice here is that our <code style="color:#ca0eef">install</code> function has two arguments. The first one is the usual JS runtime. While the second one is the current instance of our class <code style="color:#9b0f1f">SimpleJsi</code>. We will need it to access the functions in Objective C.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The number of arguments for this function is 0.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ab05c1">[simpleJsi]</code>We are passing simpleJsi instance to our lambda function because variables cannot be implicitly captured in a lambda with no capture-default specified.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#2f6d00">convertNSStringToJSIString</code> From YeetJSIUtils which helps us to convert <code style="color:#b2120c">NSString</code> to <code style="color:#8e0c2d">JSIString</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Finally we are returning our device model name.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Now let's also add the remaining two functions <code style="color:#029126">setItem</code> and <code style="color:#0ba399">getItem</code>:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-objc"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">auto</span> setItem <span class="token operator">=</span> Function<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">createFromHostFunction</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="2"></span>jsiRuntime<span class="token punctuation">,</span> PropNameID<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">forAscii</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"setItem"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span><span class="token punctuation">[</span>simpleJsi<span class="token punctuation">]</span><span class="token punctuation">(</span>Runtime <span class="token operator">&amp;</span>runtime<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">&amp;</span>thisValue<span class="token punctuation">,</span>
<span class="ln-num" data-num="4"></span><span class="token keyword">const</span> Value \<span class="token operator">*</span>arguments<span class="token punctuation">,</span> size_t count<span class="token punctuation">)</span> <span class="token operator">-&gt;</span> Value <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span>
<span class="ln-num" data-num="6"></span>      NSString <span class="token operator">*</span>key <span class="token operator">=</span>
<span class="ln-num" data-num="7"></span>          <span class="token function">convertJSIStringToNSString</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> arguments<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span>runtime<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span>
<span class="ln-num" data-num="9"></span>      NSString <span class="token operator">*</span>value <span class="token operator">=</span>
<span class="ln-num" data-num="10"></span>          <span class="token function">convertJSIStringToNSString</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> arguments<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span>runtime<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span>
<span class="ln-num" data-num="12"></span>      <span class="token punctuation">[</span>simpleJsi setItem<span class="token punctuation">:</span>key<span class="token punctuation">:</span>value<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>
<span class="ln-num" data-num="14"></span>      <span class="token keyword">return</span> <span class="token function">Value</span><span class="token punctuation">(</span>true<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="16"></span>
<span class="ln-num" data-num="17"></span>jsiRuntime<span class="token punctuation">.</span><span class="token function">global</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"setItem"</span><span class="token punctuation">,</span> <span class="token function">move</span><span class="token punctuation">(</span>setItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="18"></span>
<span class="ln-num" data-num="19"></span><span class="token keyword">auto</span> getItem <span class="token operator">=</span> Function<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">createFromHostFunction</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="20"></span>jsiRuntime<span class="token punctuation">,</span> PropNameID<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">forAscii</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"getItem"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="21"></span><span class="token punctuation">[</span>simpleJsi<span class="token punctuation">]</span><span class="token punctuation">(</span>Runtime <span class="token operator">&amp;</span>runtime<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">&amp;</span>thisValue<span class="token punctuation">,</span>
<span class="ln-num" data-num="22"></span><span class="token keyword">const</span> Value \<span class="token operator">*</span>arguments<span class="token punctuation">,</span> size_t count<span class="token punctuation">)</span> <span class="token operator">-&gt;</span> Value <span class="token punctuation">{</span>
<span class="ln-num" data-num="23"></span>
<span class="ln-num" data-num="24"></span>      NSString <span class="token operator">*</span>key <span class="token operator">=</span>
<span class="ln-num" data-num="25"></span>          <span class="token function">convertJSIStringToNSString</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> arguments<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span>runtime<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="26"></span>
<span class="ln-num" data-num="27"></span>      NSString <span class="token operator">*</span>value <span class="token operator">=</span> <span class="token punctuation">[</span>simpleJsi getItem<span class="token punctuation">:</span>key<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="28"></span>
<span class="ln-num" data-num="29"></span>      <span class="token keyword">return</span> <span class="token function">Value</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> <span class="token function">convertNSStringToJSIString</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="30"></span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="31"></span>
<span class="ln-num" data-num="32"></span>jsiRuntime<span class="token punctuation">.</span><span class="token function">global</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"getItem"</span><span class="token punctuation">,</span> <span class="token function">move</span><span class="token punctuation">(</span>getItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Since both of these functions have arguments we are using <code style="color:#078452">convertJSIStringToNSString</code> to convert <code style="color:#8e0c2d">JSIString</code> to <code style="color:#b2120c">NSString</code>. The remaining is similar to our <code style="color:#188400">getDeviceName</code> function.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">One more thing to note is that we are not using<code style="color:#b80adb">[self setItem::]</code> function in Objective C class even though our install function is inside the class. This is because we can't use it directly in C++.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And now we run our app and there you go. All good and running on iOS too.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/iphone-module._nqMVJFj.webp 364w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/iphone-module.DXl9egz8.png 364w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/iphone-module.DXl9egz8.png" srcset="https://blog.notesnook.com/assets/static/iphone-module.DXl9egz8.png 364w" sizes="100vw" alt="JSI module running on phone" style="max-width:364px" class="bdr_default w_full"></picture></figure></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Conclusion</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Using the code above you can convert any Native Module to a React Native JSI module. Writing the boilerplate for React Native JSI seems daunting but JSI is, by far, the best way to deliver native performance to your users. I guess the extra work is worth it, eh?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And the best part? No unnecessary Promise based APIs. No unnecessary conversion of parameters. No overhead. Everything happens on a single layer giving the best possible performance (and the best interopability).</p>
<style data-emotion="css fsju5r">.css-fsju5r{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><style data-emotion="css xmhgif">.css-xmhgif{box-sizing:border-box;margin:0;min-width:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><div class="css-xmhgif"><p style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can find the whole code of the library + examples <a href="https://github.com/ammarahm-ed/react-native-simple-jsi" target="_blank" class="c_accent">on
Github</a>.</p><p></p></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I am actively using React Native JSI for encryption &amp; storage in Notesnook Android &amp; iOS apps. You can get the apps from <a href="https://notesnook.com/" target="_blank" class="c_accent">here</a>.</p>]]></content:encoded>
            <author>Ammar Ahmed</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.CEUYVYQi.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Privacy on WhatsApp: Are We Ready for a World Without Privacy of Data?]]></title>
            <link>https://blog.notesnook.com/privacy-on-whatsapp-is-a-joke</link>
            <guid isPermaLink="false">https://blog.notesnook.com/privacy-on-whatsapp-is-a-joke</guid>
            <pubDate>Mon, 26 Jul 2021 12:01:22 GMT</pubDate>
            <description><![CDATA[We are unknowingly beckoning the devil: a world where our every word will be watched and sold. The changes to Whatsapp privacy policy are a clear indication.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In early January of 2021, Whatsapp gave an ultimatum to its 2 billion-plus userbase: accept our updated privacy policy or leave. Privacy on Whatsapp was already a controversial topic but this public ultimatum tipped the bucket.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">People who were already privacy-conscious started to migrate to other apps like Telegram or Signal. <a href="https://twitter.com/elonmusk/status/1347165127036977153" target="_blank" class="c_accent">Elon Musk tweeted</a> telling users to "use Signal" which caused <a href="https://www.independent.co.uk/life-style/gadgets-and-tech/whatsapp-privacy-telegram-world-leaders-b1787218.html" target="_blank" class="c_accent">the largest digital migration in history of the Internet</a>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But a lot of people already knew that privacy on Whatsapp is a joke. Whatsapp had made <a href="https://www.wired.com/2016/08/whatsapp-privacy-facebook/" target="_blank" class="c_accent">similar privacy policy updates</a> before. And it had never received such a riotous response from its users. What had changed?</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">The inability to opt-out</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The obvious reason for the general outcry against the privacy policy was the sweeping changes that Whatsapp made in one go. In short, <a href="https://arstechnica.com/tech-policy/2021/01/whatsapp-users-must-share-their-data-with-facebook-or-stop-using-the-app/" target="_blank" class="c_accent">Whatsapp was asking permission to share all of its user metadata with its parent company Facebook</a>. What was this data that Whatsapp was going to share? It was your phone number, your contact's phone numbers, transaction data, information on how you interact with others (including businesses), your phone's IP address, your location data, your activity patterns, profile pictures...the list goes on.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Facebook is notorious for mishandling its user's privacy of data. The 2015 Cambridge Analytica scandal was still fresh in users' minds and when WhatsApp changed its privacy policy, the implications were clear: it was time to start using the data of its 2 billion users for ads and monetization.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">The exemption of some countries like Europe</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Interestingly some countries like the EU were completely exempt from these privacy ultimatums. No changes were made in the privacy policy for users in the EU.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is no surprise as the EU has very strict privacy laws. In 2016 Facebook was fined $122 million in fines because it mislead the EU regulators into believing that Whatsapp and Facebook had no possibility of integration with each other. The very thing it was now doing openly.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Whatsapp's Perfect Trap</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This exemption of users from some countries was infuriating for others who were given no choice. Whatsapp had become such an important part of our lives that a lot of people had to accept the new privacy policy simply because their friends and family were not on any other platform. In simple words, Whatsapp had concocted the perfect trap.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">A future without privacy</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In the ever changing world of Internet, privacy of data has never been important for anyone. Most of us use Google to find answers to our questions, Whatsapp &amp; Facebook to communicate with our friends and family. To us these are just tools like an axe or a saw for a carpenter. A means to an end.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/bigdata.5GYJq87C.webp 640w" sizes="100vw"><source type="image/jpg" srcset="https://blog.notesnook.com/assets/static/bigdata.zb0s9NPm.jpg 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/bigdata.zb0s9NPm.jpg" srcset="https://blog.notesnook.com/assets/static/bigdata.zb0s9NPm.jpg 640w" sizes="100vw" alt="Big data is watching you" style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Photo by ev on Unsplash</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Unfortunately, we have fed the giant and continue to feed it. The future of the Internet is bleak with nothing hidden from prying eyes. Why? Because we let these so-called tools establish themselves as essentials in our lives. Can you imagine spending a day without Whatsapp or Facebook or Instagram?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But the big question is: are we ready for a world without privacy of data?</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">A mass surveillance world</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We, the users, are unknowingly beckoning the devil: a world where our every thought, every word will be watched, processed, used, and sold.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The leaks by Edward Snowden and Facebook's Cambridge Analytica Scandal are a clear warning for all of us. Is it too late to change this? The problem is clear but are we ready for the solution?</p>
<style data-emotion="css fsju5r">.css-fsju5r{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><style data-emotion="css xmhgif">.css-xmhgif{box-sizing:border-box;margin:0;min-width:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><div class="css-xmhgif"><p style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"></p><h4 style="margin-top:0">A telegraph from Notesnook</h4>
Dear reader,<br>
<!-- -->I do not fancy a world where privacy is a foriegn concept. That is the very reason for which I was made. To carry the flag of privacy, freedom, and digital liberty. To prove that utility can exist with privacy. To say no to invasion of privacy.<p></p><p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Yours truly,<br>
<a href="https://notesnook.com/" target="_blank" class="c_accent">Notesnook</a></p><p></p></div>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Privacy</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.CmAPMgV8.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[A More Native Desktop Experience, Faster Sync, Vault Deletion & Improved Account Security]]></title>
            <link>https://blog.notesnook.com/notesnook-1-4-1-update</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-1-4-1-update</guid>
            <pubDate>Thu, 08 Jul 2021 12:01:22 GMT</pubDate>
            <description><![CDATA[This release feels more like v1.5 than v1.4.1 but eh... there's now a more native desktop experience, a faster sync, better security, and improved UX.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This release packs so much that it feels more like v1.5 than v1.4.1 but eh, what can I say, it is what it is! Anyway, time to show what's new.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">TLDR; Jump away those features!</h3>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a10m" target="_blank" class="c_accent">More epicness in our epic editor</a>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a10n" target="_blank" class="c_accent">Collapsible headers</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a10o" target="_blank" class="c_accent">More features for the free loaders</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a10p" target="_blank" class="c_accent">No more Pro popups</a></li>
</ul>
</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a10q" target="_blank" class="c_accent">Multi select that'll change your life</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a10r" target="_blank" class="c_accent">Better syncing for all you multi device folks</a>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a10s" target="_blank" class="c_accent">Consistent sync status across devices</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a10t" target="_blank" class="c_accent">No more unexpected conflicts</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a10u" target="_blank" class="c_accent">Faster automatic sync</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a10v" target="_blank" class="c_accent">Force sync for when things get stuck</a></li>
</ul>
</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a110" target="_blank" class="c_accent">Finally a more native desktop app</a>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a111" target="_blank" class="c_accent">Automatic backups</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a112" target="_blank" class="c_accent">Auto updating indicator</a></li>
</ul>
</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a113" target="_blank" class="c_accent">Security, security, security</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a114" target="_blank" class="c_accent">More fun in the vault</a>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a115" target="_blank" class="c_accent">Introducing vault deletion</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a116" target="_blank" class="c_accent">And you can also clear your vault</a></li>
</ul>
</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/notesnook-1-4-1-update/#mcetoc_1fa238l9a117" target="_blank" class="c_accent">Not to mention the many bugs we squashed</a></li>
</ul>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">More epicness in our epic editor</h2>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Collapsible headers</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Writing long documents is a gruesome, ugly, and slow task. No more! Because, apparently, you can deceive yourself by collapsing those "big" headers i.e. hiding those ugly details until you really need them.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The cool thing is that collapsed headers save with the note. And they sync too! So no matter where you are, you won't have to look at those ugly, messy details. Not even by accident.s</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><img alt="Collapsible headers in editor" src="https://blog.notesnook.com/img/collapse_headers.gif" class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><figcaption class="w_full ta_center font-style_italic fs_s c_info">Look, I'm collapsible ma!</figcaption></figure></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">More features for the free loaders</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Who doesn't like free stuff, eh?!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Don't tell the Pro users but...</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Lists (aside from checklists), subscript, superscript, indentation, and links are now available for all users. Free and Pro.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">No more Pro popups</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Remember how when you tried to add a checklist it opened a full blown dialog yik yak-ing about Notesnook Pro? Yep, that was super annoying.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I noticed it. And I fixed it.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Now you'll see a cute toast at the top right corner whenever you accidentally use a premium feature.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><img alt="No more annoying Pro popups" src="https://blog.notesnook.com/img/annoying_popups.gif" class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><figcaption class="w_full ta_center font-style_italic fs_s c_info">No more annoying Pro popups</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Nice eh?</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Multi select that'll change your life</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Literally.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I know not many people use multi-select (or even know if it exists) but you don't know when you'll need it.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Previously, there was no Select All in multi-select. How absurd! Thankfully, I took notice and fixed it. Yay!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But that's not all...the context menu when you have multiple items selected now show the multi select actions instead of item specific actions. Which makes a lot more sense, if you ask me.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/multi-select-md.DCkW5hH0.webp 596w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/multi-select-md.BSbp4-OL.png 596w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/multi-select-md.BSbp4-OL.png" srcset="https://blog.notesnook.com/assets/static/multi-select-md.BSbp4-OL.png 596w" sizes="100vw" alt="A multi-select that actually multi-selects." style="max-width:596px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">A multi-select that actually multi-selects.</figcaption></figure></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Better syncing for all you multi device folks</h2>
<div sx="[object Object]" class=""></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Consistent sync status across devices</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If you use Notesnook on multiple devices, you know how confusing it is when one device says, "Synced 1 hour ago" while the other says, "Synced 5 minutes ago".</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">That's now fixed. All devices will <em>try</em> to show the same sync status. I say try because...</p>
<div sx="[object Object]" class="d_flex"><div sx="[object Object]" class=""></div><p sx="[object Object]" style="font-size:var(--font-sizes-sm)" class="c_paragraph">Time is a fickle thing, my friend.</p><div sx="[object Object]" class=""></div></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">No more unexpected conflicts</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Okay maybe you never noticed this. It happened very rarely but it was annoying.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Sometimes, you'd get conflicts while working on a single device. Wait...what?! Yeah. I know. Confusing. But it happened.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Fortunately, we fixed it.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Faster automatic sync</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Good news to all you Pro users: automatic sync time is now 5 seconds from 15 seconds. That means...</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Changes now sync in real time, almost.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Force sync for when things get stuck</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Have a note that didn't sync? No problemo! Just force sync and you are good to go.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/sync-problems-md.CcL6lAQB.webp 400w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/sync-problems-md.B7C6imsy.png 400w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/sync-problems-md.B7C6imsy.png" srcset="https://blog.notesnook.com/assets/static/sync-problems-md.B7C6imsy.png 400w" sizes="100vw" alt="Fix sync problems with force sync" style="max-width:400px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Fix sync problems with force sync</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Although, don't over use it since it syncs all your data both ends...and if you have a lot of notes, I'd think twice before clicking that button.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Finally a more native desktop app</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For the past several releases, I have ignored the desktop app. It was <em>just</em> a wrapper around the web app, they'd say but not anymore.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Automatic backups</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Another good news for the Pro users:</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Instead of just reminding you to backup your data after a set interval, the desktop app will now actually do backups automatically in the background! How cool! No more data loss! No more data corruption! No more worries!</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But make sure you turn automatic backups on...though.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Auto updating indicator</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">I don't know if you noticed or not but the desktop app updates automatically in the background. Yep.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Well that was fun but after this update, you'll see a cool indicator in the bottom status bar. Telling you if there's a new update, progress of the update being downloaded, and if the update is ready to be installed.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Security, security, security</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A private app is only as private as it's secure.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are always thinking of ways to improve our app's security. Not just against our servers but against your data on your device as well.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To that end, we are now putting some things under lock and key. Meaning, things like your account's data recovery key - the most important thing keeping your notes safe won't be accessible to just about anyone.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><img alt="No more annoying Pro popups" src="https://blog.notesnook.com/img/annoying_popups.gif" class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><figcaption class="w_full ta_center font-style_italic fs_s c_info">No more annoying Pro popups</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When you will try to access your data recovery key, it'll now ask you to verify yourself. Cool, no? Same thing when you try to create a backup manually.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">More fun in the vault</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Our vault has come a long way. Seriously. There was a time when locked notes couldn't be unlocked. Or when 2 devices could suddenly make 2 separate vaults. The bugs never really ended.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But the most important (yet non-existent) thing was vault recovery - or a way to create a new vault in case you forgot the password to your old one.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Introducing vault deletion</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Cool intro eh? Yep, you can now delete your vault (and optionally, delete all the locked notes as well). You need your account password to do this - instead of the vault password.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/delete_vault.Cea6Wp2F.webp 389w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/delete_vault.DTzUWEMN.png 389w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/delete_vault.DTzUWEMN.png" srcset="https://blog.notesnook.com/assets/static/delete_vault.DTzUWEMN.png 389w" sizes="100vw" alt="Delete vault" style="max-width:389px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Delete vault</figcaption></figure></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">And you can also clear your vault</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Want to unlock all your locked notes at once? Clear the vault!</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Not to mention the many bugs we squashed</h2>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">You can now restore encrypted backups</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Backups created are now a little bit smaller</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Deleting a notebook now properly deletes the notebook reference from all linked notes.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Deleting a note no longer gets stuck if it's not found in a tag it says it's in.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Removing a locked note now unlocks it beforehand.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">App no longer crashes when opening a notebook without a topic.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And many more.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Try the new version</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As always, all these features are 100% cross-platform. You can install our Android &amp; iOS apps from their respective stores.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For desktop, you can go and <a href="https://notesnook.com/" target="_blank" class="c_accent">download for your platform from our website</a>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For quickly trying out the new version, you can just <a href="https://app.notesnook.com/" target="_blank" class="c_accent">use our web app.</a></p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Ready to go pro?</h2>
<style data-emotion="css fsju5r">.css-fsju5r{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><style data-emotion="css xmhgif">.css-xmhgif{box-sizing:border-box;margin:0;min-width:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><div class="css-xmhgif"><p style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><p sx="[object Object]" style="font-size:var(--font-sizes-sm)" class="c_paragraph">Get 15% off yearly and monthly plan. Use coupon code YEAR15. <a sx="[object Object]" href="https://app.notesnook.com/#/buy/YEAR15" style="font-size:var(--font-sizes-sm)" class="c_paragraph">Subscribe now</a></p><p></p></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Stay updated</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We recommend that you <a href="https://twitter.com/notesnook" target="_blank" class="c_accent">follow us on Twitter</a> and <a href="https://discord.com/invite/zQBK97EE22" target="_blank" class="c_accent">join our Discord server</a> to stay up to date with all the new features coming to Notesnook. We also provide 24/7 support to all our users on Discord.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.DvBai-Wx.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[React Native JSI: Part 1 - Getting Started]]></title>
            <link>https://blog.notesnook.com/getting-started-react-native-jsi</link>
            <guid isPermaLink="false">https://blog.notesnook.com/getting-started-react-native-jsi</guid>
            <pubDate>Mon, 28 Jun 2021 12:01:22 GMT</pubDate>
            <description><![CDATA[React Native JSI (Javascript Interface) is the new layer that helps in communication between Javascript and Native Platforms easier and faster.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">React Native JSI (Javascript Interface) is the new layer that helps in communication between Javascript and Native Platforms easier and faster. It is the core element in re-architecture of React Native with Fabric UI Layer and Turbo Modules.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">How is JSI different?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">JSI removes the need for a <code style="color:#910240">bridge</code> between Native(Java/ObjC) and Javascript code. It also removes the requirement to serialize/deserialize all the information as JSON for communication between the two worlds. JSI is opening doors to new possibilities by bringing closes the javascript and the native worlds. Based on my understanding I am going to help you understand more about the JSI interface based on my knowledge.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Javascript Interface which allows us to register methods with the Javascript runtime. These methods are available via the <code style="color:#a102c9">global</code> object in the Javascript world.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The methods can be entirely written in C++ or they can be a way to communicate with Objective C code on iOS and Java code in Android.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Any native module that is currently using the traditional <code style="color:#910240">bridge</code> for communication between Javascript and the native worlds can be converted to a JSI module by writing a simple layer in C++</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">On iOS writing this layer is simple because C++ can run directly in Objective C hence all the iOS frameworks and code is available to use directly.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">On android however we have to go an extra mile to do this through JNI.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">These methods can be fully synchronous which means using <code style="color:#d615b2">async/await</code> is not mandatory.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Now we are going to create a simple JSI Module which will help us understand everything even better.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Setting up our JSI Module</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Open terminal in the desired directory where you want to create your library and run the following:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-bash"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>npx create-react-native-library react-native-simple-jsi</code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It will ask you some questions.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/nativemodule_cli.By-ByFDS.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/nativemodule_cli.BT46DCuQ.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/nativemodule_cli.BT46DCuQ.png" srcset="https://blog.notesnook.com/assets/static/nativemodule_cli.BT46DCuQ.png 640w" sizes="100vw" alt="Generating a native module with cli" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The important part is to choose <code style="color:#c914a8">C++ for iOS and Android</code> when it asks for <code style="color:#0e9e64">Which languages you want to use?</code></p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/nativemodule_cli_1.D-Yt9exn.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/nativemodule_cli_1.CbjlFmCZ.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/nativemodule_cli_1.CbjlFmCZ.png" srcset="https://blog.notesnook.com/assets/static/nativemodule_cli_1.CbjlFmCZ.png 640w" sizes="100vw" alt="Generating a native module with cli" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This will setup a basic module for us that uses C++ code. However note that this is not a JSI module. We need to change some parts of the code on Android and iOS to make it a JSI module.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Navigate to the <code style="color:#089126">react-native-simple-jsi</code> folder that was just created and <strong>delete the example folder</strong> then create a new example in its place.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">npx react-native init example.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">It will also resolve all the other dependencies.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Configuring on Android</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Let's configure our library for android.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Prerequisites for android:</strong></p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Install NDK.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Install CMake.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can install both of these from SDK Manager in Android Studio</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2"><code style="color:#d80fd2">CMakeLists.txt</code></h3>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-cpp"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token function">cmake_minimum_required</span><span class="token punctuation">(</span>VERSION <span class="token number">3.9</span><span class="token punctuation">.</span><span class="token number">0</span><span class="token punctuation">)</span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span><span class="token function">add_library</span><span class="token punctuation">(</span>cpp
<span class="ln-num" data-num="4"></span>            SHARED
<span class="ln-num" data-num="5"></span>            <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token operator">/</span>cpp<span class="token operator">/</span>example<span class="token punctuation">.</span>cpp
<span class="ln-num" data-num="6"></span>            <span class="token punctuation">.</span><span class="token operator">/</span>cpp<span class="token operator">-</span>adapter<span class="token punctuation">.</span>cpp
<span class="ln-num" data-num="7"></span><span class="token punctuation">)</span>
<span class="ln-num" data-num="8"></span>
<span class="ln-num" data-num="9"></span><span class="token function">include_directories</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="10"></span>            <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token operator">/</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token operator">/</span>react<span class="token operator">-</span>native<span class="token operator">/</span>React
<span class="ln-num" data-num="11"></span>            <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token operator">/</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token operator">/</span>react<span class="token operator">-</span>native<span class="token operator">/</span>React<span class="token operator">/</span>Base
<span class="ln-num" data-num="12"></span>            <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token operator">/</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token operator">/</span>react<span class="token operator">-</span>native<span class="token operator">/</span>ReactCommon<span class="token operator">/</span>jsi
<span class="ln-num" data-num="13"></span>            <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token operator">/</span>cpp
<span class="ln-num" data-num="14"></span><span class="token punctuation">)</span>
<span class="ln-num" data-num="15"></span>
<span class="ln-num" data-num="16"></span><span class="token function">set_target_properties</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="17"></span>        cpp PROPERTIES
<span class="ln-num" data-num="18"></span>        CXX_STANDARD <span class="token number">17</span>
<span class="ln-num" data-num="19"></span>        CXX_EXTENSIONS OFF
<span class="ln-num" data-num="20"></span>        POSITION_INDEPENDENT_CODE ON
<span class="ln-num" data-num="21"></span><span class="token punctuation">)</span>
<span class="ln-num" data-num="22"></span>
<span class="ln-num" data-num="23"></span><span class="token function">target_link_libraries</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="24"></span>        cpp
<span class="ln-num" data-num="25"></span>        android
<span class="ln-num" data-num="26"></span><span class="token punctuation">)</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Okay, let's make this consumable. We are linking all the different libraries and files that we need for our jsi module. We are telling CMake(Compiler for C++) how to compile our code and what directories to look for dependencies.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><code style="color:#303030">cmake_minimum_required</code>: The minimum version of CMake required to compile our library.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><code style="color:#1c1c1c">add_library</code>: We are telling the compiler, which libraries to add.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#d906e8">cpp</code> is the name of our library.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#23028e">SHARED</code> libraries are linked dynamically and loaded at runtime.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We are including different files that we will need for our code to run.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#c403c4">example.cpp</code> is where our c++ code lives</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><code style="color:#2b2b2b">include_directories</code>: Here we are telling the compiler to search for include files.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Remember to change <code style="color:#d906e8">cpp</code> to your desirable library name here.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2"><code style="color:#d60497">build.gradle</code></h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Specify the minimum version of CMake to use while compiling c++ code.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-java"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>  externalNativeBuild <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>    cmake <span class="token punctuation">{</span>
<span class="ln-num" data-num="3"></span>      path <span class="token string">"./CMakeLists.txt"</span>
<span class="ln-num" data-num="4"></span>      version <span class="token string">"3.8.0+"</span>
<span class="ln-num" data-num="5"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="6"></span>  <span class="token punctuation">}</span></code></pre></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Step 3: Installing JSI Bindings</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Run <code style="color:#d10de2">yarn add ../</code> inside the example folder to add our library to the example project.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Open <code style="color:#db0ab5">example/android</code> folder in Android Studio and wait for gradle to complete building your project.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If everything went as planned you should now see this in the Sidebar in Android</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/androidstudio.aOgv3s1S.webp 627w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/androidstudio.CeC3kgtk.png 627w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/androidstudio.CeC3kgtk.png" srcset="https://blog.notesnook.com/assets/static/androidstudio.CeC3kgtk.png 627w" sizes="100vw" alt="JSI module android studio folder layout" style="max-width:627px" class="bdr_default w_full"></picture></figure></div>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1"><code style="color:#d114a2">SimpleJsiModule.java</code></h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">From the Sidebar navigate to <code style="color:#d211d8">react-native-simple-jsi/android/java/com.reactnativesimplejsi/SimpleJsiModule.java</code> and replace it with the following code:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-java"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">package</span> <span class="token namespace">com<span class="token punctuation">.</span>reactnativesimplejsi</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">android<span class="token punctuation">.</span>util<span class="token punctuation">.</span></span><span class="token class-name">Log</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span>
<span class="ln-num" data-num="5"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">androidx<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span></span><span class="token class-name">NonNull</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>
<span class="ln-num" data-num="7"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>facebook<span class="token punctuation">.</span>react<span class="token punctuation">.</span>bridge<span class="token punctuation">.</span></span><span class="token class-name">JavaScriptContextHolder</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>facebook<span class="token punctuation">.</span>react<span class="token punctuation">.</span>bridge<span class="token punctuation">.</span></span><span class="token class-name">ReactApplicationContext</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>facebook<span class="token punctuation">.</span>react<span class="token punctuation">.</span>bridge<span class="token punctuation">.</span></span><span class="token class-name">ReactContextBaseJavaModule</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="10"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>facebook<span class="token punctuation">.</span>react<span class="token punctuation">.</span>module<span class="token punctuation">.</span>annotations<span class="token punctuation">.</span></span><span class="token class-name">ReactModule</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span>
<span class="ln-num" data-num="12"></span><span class="token annotation punctuation">@ReactModule</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token class-name">SimpleJsiModule</span><span class="token punctuation">.</span><span class="token constant">NAME</span><span class="token punctuation">)</span>
<span class="ln-num" data-num="13"></span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SimpleJsiModule</span> <span class="token keyword">extends</span> <span class="token class-name">ReactContextBaseJavaModule</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="14"></span>  <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> <span class="token constant">NAME</span> <span class="token operator">=</span> <span class="token string">"SimpleJsi"</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>
<span class="ln-num" data-num="16"></span>  <span class="token keyword">static</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="17"></span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="18"></span>      <span class="token comment">// Used to load the 'native-lib' library on application startup.</span>
<span class="ln-num" data-num="19"></span>      <span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">loadLibrary</span><span class="token punctuation">(</span><span class="token string">"cpp"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="20"></span>    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ignored<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="21"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="22"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="23"></span>
<span class="ln-num" data-num="24"></span>  <span class="token keyword">public</span> <span class="token class-name">SimpleJsiModule</span><span class="token punctuation">(</span><span class="token class-name">ReactApplicationContext</span> reactContext<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="25"></span>    <span class="token keyword">super</span><span class="token punctuation">(</span>reactContext<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="26"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="27"></span>
<span class="ln-num" data-num="28"></span>  <span class="token annotation punctuation">@Override</span>
<span class="ln-num" data-num="29"></span>  <span class="token annotation punctuation">@NonNull</span>
<span class="ln-num" data-num="30"></span>  <span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="31"></span>    <span class="token keyword">return</span> <span class="token constant">NAME</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="32"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="33"></span>
<span class="ln-num" data-num="34"></span>  <span class="token keyword">private</span> <span class="token keyword">native</span> <span class="token keyword">void</span> <span class="token function">nativeInstall</span><span class="token punctuation">(</span><span class="token keyword">long</span> jsi<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="35"></span>
<span class="ln-num" data-num="36"></span>  <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">installLib</span><span class="token punctuation">(</span><span class="token class-name">JavaScriptContextHolder</span> reactContext<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="37"></span>
<span class="ln-num" data-num="38"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>reactContext<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="39"></span>      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">nativeInstall</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="40"></span>        reactContext<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="ln-num" data-num="41"></span>      <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="42"></span>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="43"></span>      <span class="token class-name">Log</span><span class="token punctuation">.</span><span class="token function">e</span><span class="token punctuation">(</span><span class="token string">"SimpleJsiModule"</span><span class="token punctuation">,</span> <span class="token string">"JSI Runtime is not available in debug mode"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="44"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="45"></span>
<span class="ln-num" data-num="46"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="47"></span>
<span class="ln-num" data-num="48"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As you see, there are no <code style="color:#d611c5">@ReactMethod</code> etc here. Two things are happening in this class.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We are loading our c++ library using <code style="color:#c400a3">System.loadLibrary</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We have an <code style="color:#028e4b">installLib</code> function which is looking for javascript runtime memory reference. The <code style="color:#ed0ec4">get</code> function basically returns a <code style="color:#d110c1">long</code> value. This value is passed over to JNI where we will install our bindings.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">But we have an error, the <code style="color:#378206">nativeInstall</code> function is not present in JNI.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/androidstudio_error.aW738SKC.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/androidstudio_error.D5JpjTrN.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/androidstudio_error.D5JpjTrN.png" srcset="https://blog.notesnook.com/assets/static/androidstudio_error.D5JpjTrN.png 640w" sizes="100vw" alt="Native bindings not found error" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Just click on Create JNI function for nativeInstall in the tooltip that shows when you move cursor over the method.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Now if you open <code style="color:#db06b4">cpp-adapter.cpp</code> file. You will see a <code style="color:#ce08ba">Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall</code> function added.</p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1"><code style="color:#e80992">SimpleJsiModulePackage.java</code></h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This file does not exist. You have to create this java class.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Create a new java class and name it <code style="color:#930c02">SimpleJsiModulePackage</code>.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/androidstudio_2.B0hRxho7.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/androidstudio_2.CayK6r1C.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/androidstudio_2.CayK6r1C.png" srcset="https://blog.notesnook.com/assets/static/androidstudio_2.CayK6r1C.png 640w" sizes="100vw" alt="Creating a new java class SimpleJsiModulePackage.java" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Replace with the following code:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-java"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">package</span> <span class="token namespace">com<span class="token punctuation">.</span>reactnativesimplejsi</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>facebook<span class="token punctuation">.</span>react<span class="token punctuation">.</span>bridge<span class="token punctuation">.</span></span><span class="token class-name">JSIModulePackage</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>facebook<span class="token punctuation">.</span>react<span class="token punctuation">.</span>bridge<span class="token punctuation">.</span></span><span class="token class-name">JSIModuleSpec</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>facebook<span class="token punctuation">.</span>react<span class="token punctuation">.</span>bridge<span class="token punctuation">.</span></span><span class="token class-name">JavaScriptContextHolder</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>facebook<span class="token punctuation">.</span>react<span class="token punctuation">.</span>bridge<span class="token punctuation">.</span></span><span class="token class-name">ReactApplicationContext</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>util<span class="token punctuation">.</span></span><span class="token class-name">Collections</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>util<span class="token punctuation">.</span></span><span class="token class-name">List</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span>
<span class="ln-num" data-num="10"></span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SimpleJsiModulePackage</span> <span class="token keyword">implements</span> <span class="token class-name">JSIModulePackage</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="11"></span>  <span class="token annotation punctuation">@Override</span>
<span class="ln-num" data-num="12"></span>  <span class="token keyword">public</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">JSIModuleSpec</span><span class="token punctuation">&gt;</span></span> <span class="token function">getJSIModules</span><span class="token punctuation">(</span><span class="token class-name">ReactApplicationContext</span> reactApplicationContext<span class="token punctuation">,</span> <span class="token class-name">JavaScriptContextHolder</span> jsContext<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="13"></span>
<span class="ln-num" data-num="14"></span>    reactApplicationContext<span class="token punctuation">.</span><span class="token function">getNativeModule</span><span class="token punctuation">(</span><span class="token class-name">SimpleJsiModule</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">installLib</span><span class="token punctuation">(</span>jsContext<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>
<span class="ln-num" data-num="16"></span>    <span class="token keyword">return</span> <span class="token class-name">Collections</span><span class="token punctuation">.</span><span class="token function">emptyList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="17"></span>  <span class="token punctuation">}</span>
<span class="ln-num" data-num="18"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In this class we are overriding the <code style="color:#14a80f">getJSIModules</code> method and installing our jsi bindings.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">At this point our module is registered and running. So we are getting the module from react context and then calling <code style="color:#028e4b">installLib</code> function to install our library.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">While we could do this directly in our native module when it loads, it would not be safe because it is possible that the runtime is not loaded when the native module is ready. This package gives us more control and makes sure that runtime is available when we call <code style="color:#028e4b">installLib</code>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">To call this method and install library we have to modify our app's <code style="color:#b70fc6">MainApplication.java</code>.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-java"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>facebook<span class="token punctuation">.</span>react<span class="token punctuation">.</span>bridge<span class="token punctuation">.</span></span><span class="token class-name">JSIModulePackage</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>reactnativesimplejsi<span class="token punctuation">.</span></span><span class="token class-name">SimpleJsiModulePackage</span></span><span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span>
<span class="ln-num" data-num="6"></span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MainApplication</span> <span class="token keyword">extends</span> <span class="token class-name">Application</span> <span class="token keyword">implements</span> <span class="token class-name">ReactApplication</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="7"></span>
<span class="ln-num" data-num="8"></span>  <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">ReactNativeHost</span> mReactNativeHost <span class="token operator">=</span>
<span class="ln-num" data-num="9"></span>      <span class="token keyword">new</span> <span class="token class-name">ReactNativeHost</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="10"></span>        <span class="token annotation punctuation">@Override</span>
<span class="ln-num" data-num="11"></span>        <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">getUseDeveloperSupport</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="12"></span>          <span class="token keyword">return</span> <span class="token class-name">BuildConfig</span><span class="token punctuation">.</span><span class="token constant">DEBUG</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>        <span class="token punctuation">}</span>
<span class="ln-num" data-num="14"></span>
<span class="ln-num" data-num="15"></span>        <span class="token annotation punctuation">@Override</span>
<span class="ln-num" data-num="16"></span>        <span class="token keyword">protected</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">ReactPackage</span><span class="token punctuation">&gt;</span></span> <span class="token function">getPackages</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="17"></span>          <span class="token annotation punctuation">@SuppressWarnings</span><span class="token punctuation">(</span><span class="token string">"UnnecessaryLocalVariable"</span><span class="token punctuation">)</span>
<span class="ln-num" data-num="18"></span>          <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">ReactPackage</span><span class="token punctuation">&gt;</span></span> packages <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PackageList</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getPackages</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="19"></span>          <span class="token comment">// Packages that cannot be autolinked yet can be added manually here, for SimpleJsiExample:</span>
<span class="ln-num" data-num="20"></span>          <span class="token comment">// packages.add(new MyReactNativePackage());</span>
<span class="ln-num" data-num="21"></span>          <span class="token keyword">return</span> packages<span class="token punctuation">;</span>
<span class="ln-num" data-num="22"></span>        <span class="token punctuation">}</span>
<span class="ln-num" data-num="23"></span>
<span class="ln-num" data-num="24"></span>        <span class="token annotation punctuation">@Override</span>
<span class="ln-num" data-num="25"></span>        <span class="token keyword">protected</span> <span class="token class-name">JSIModulePackage</span> <span class="token function">getJSIModulePackage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="26"></span>          <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">SimpleJsiModulePackage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="27"></span>        <span class="token punctuation">}</span>
<span class="ln-num" data-num="28"></span>
<span class="ln-num" data-num="29"></span>        <span class="token annotation punctuation">@Override</span>
<span class="ln-num" data-num="30"></span>        <span class="token keyword">protected</span> <span class="token class-name">String</span> <span class="token function">getJSMainModuleName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="31"></span>          <span class="token keyword">return</span> <span class="token string">"index"</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="32"></span>        <span class="token punctuation">}</span>
<span class="ln-num" data-num="33"></span>      <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="34"></span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span></code></pre></div>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We are importing <code style="color:#910727">JSIModulePackage</code></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We are registering our <code style="color:#930c02">SimpleJsiModulePackage</code> as a JSI Module so that when JS Runtime loads, our jsi bindings are also installed. Inside our instance of <code style="color:#b53f0c">ReactNativeHost</code> we are overriding <code style="color:#087f71">getJSIModulePackage</code> method and returning an new instance of <code style="color:#930c02">SimpleJsiModulePackage</code>.</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2"><code style="color:#db06b4">cpp-adapter.cpp</code></h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">This is our Java Native Interface (JNI) adapter which allows for two way communication between java and native c++ code. We can call c++ code from java and java code from c++.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Here is how our adapter looks like.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-cpp"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;jni.h&gt;</span></span>
<span class="ln-num" data-num="2"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"example.h"</span></span>
<span class="ln-num" data-num="3"></span>
<span class="ln-num" data-num="4"></span><span class="token keyword">extern</span> <span class="token string">"C"</span>
<span class="ln-num" data-num="5"></span>JNIEXPORT <span class="token keyword">void</span> JNICALL
<span class="ln-num" data-num="6"></span><span class="token function">Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall</span><span class="token punctuation">(</span>JNIEnv <span class="token operator">*</span>env<span class="token punctuation">,</span> jobject thiz<span class="token punctuation">,</span> jlong jsi<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="7"></span>    <span class="token comment">// TODO: implement nativeInstall()</span>
<span class="ln-num" data-num="8"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Let's add JSI Bindings now assuming that <code style="color:#d30eaf">example</code> includes our <code style="color:#ca0eef">install</code> function which I will explain later.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-cpp"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;jni.h&gt;</span></span>
<span class="ln-num" data-num="2"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"example.h"</span></span>
<span class="ln-num" data-num="3"></span>
<span class="ln-num" data-num="4"></span><span class="token keyword">extern</span> <span class="token string">"C"</span>
<span class="ln-num" data-num="5"></span>JNIEXPORT <span class="token keyword">void</span> JNICALL
<span class="ln-num" data-num="6"></span><span class="token function">Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall</span><span class="token punctuation">(</span>JNIEnv <span class="token operator">*</span>env<span class="token punctuation">,</span> jobject thiz<span class="token punctuation">,</span> jlong jsi<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="7"></span>
<span class="ln-num" data-num="8"></span>    <span class="token keyword">auto</span> runtime <span class="token operator">=</span> <span class="token generic-function"><span class="token function">reinterpret_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>facebook<span class="token double-colon punctuation">::</span>jsi<span class="token double-colon punctuation">::</span>Runtime <span class="token operator">*</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>jsi<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span>
<span class="ln-num" data-num="10"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>runtime<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="11"></span>        example<span class="token double-colon punctuation">::</span><span class="token function">install</span><span class="token punctuation">(</span><span class="token operator">*</span>runtime<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="12"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="13"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are calling <code style="color:#bf1170">example::install</code> from our <code style="color:#378206">nativeInstall</code> function which is called from java code.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi)</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#a30e40">JNIEnv</code>: A JNI interface pointer</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#db17ed">jobject</code>: The java class from which the function is called.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#d110c1">long</code> value of our runtime memory reference.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are reinterpreting the runtime class with <code style="color:#be05c4">auto runtime = reinterpret_cast&lt;jsi::Runtime *&gt;(jsi);</code> and then calling <code style="color:#bf0f99">install(*runtime);</code> to install our bindings.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Configuring on iOS</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Configuration on iOS is easier than android and includes a few simple steps.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Run pod install in example/ios and open example.xcworkspace in xcode.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">SimpleJsi.mm</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Navigate to Pods &gt; Development Pods &gt; react-native-simple-jsi &gt; ios and open SimpleJsi.mm.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/xcode.Rm3As2FW.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/xcode.BEsB8jdo.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/xcode.BEsB8jdo.png" srcset="https://blog.notesnook.com/assets/static/xcode.BEsB8jdo.png 640w" sizes="100vw" alt="Configure JSI module on iOS" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Replace it with following code:</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-objc"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">import</span> <span class="token string">"SimpleJsi.h"</span></span>
<span class="ln-num" data-num="2"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">import</span> <span class="token expression"><span class="token operator">&lt;</span>React<span class="token operator">/</span>RCTBridge<span class="token operator">+</span>Private<span class="token punctuation">.</span>h<span class="token operator">&gt;</span></span></span>
<span class="ln-num" data-num="3"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">import</span> <span class="token expression"><span class="token operator">&lt;</span>React<span class="token operator">/</span>RCTUtils<span class="token punctuation">.</span>h<span class="token operator">&gt;</span></span></span>
<span class="ln-num" data-num="4"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">import</span> <span class="token expression"><span class="token operator">&lt;</span>jsi<span class="token operator">/</span>jsi<span class="token punctuation">.</span>h<span class="token operator">&gt;</span></span></span>
<span class="ln-num" data-num="5"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">import</span> <span class="token string">"example.h"</span></span>
<span class="ln-num" data-num="6"></span>
<span class="ln-num" data-num="7"></span><span class="token keyword">@implementation</span> SimpleJsi
<span class="ln-num" data-num="8"></span>
<span class="ln-num" data-num="9"></span><span class="token keyword">@synthesize</span> bridge <span class="token operator">=</span> _bridge<span class="token punctuation">;</span>
<span class="ln-num" data-num="10"></span><span class="token keyword">@synthesize</span> methodQueue <span class="token operator">=</span> _methodQueue<span class="token punctuation">;</span>
<span class="ln-num" data-num="11"></span>
<span class="ln-num" data-num="12"></span><span class="token function">RCT_EXPORT_MODULE</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="ln-num" data-num="13"></span>
<span class="ln-num" data-num="14"></span><span class="token operator">+</span> <span class="token punctuation">(</span>BOOL<span class="token punctuation">)</span>requiresMainQueueSetup <span class="token punctuation">{</span>
<span class="ln-num" data-num="15"></span>
<span class="ln-num" data-num="16"></span>    <span class="token keyword">return</span> YES<span class="token punctuation">;</span>
<span class="ln-num" data-num="17"></span><span class="token punctuation">}</span>
<span class="ln-num" data-num="18"></span>
<span class="ln-num" data-num="19"></span><span class="token operator">-</span> <span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span>setBridge<span class="token punctuation">:</span><span class="token punctuation">(</span>RCTBridge <span class="token operator">*</span><span class="token punctuation">)</span>bridge <span class="token punctuation">{</span>
<span class="ln-num" data-num="20"></span>    _bridge <span class="token operator">=</span> bridge<span class="token punctuation">;</span>
<span class="ln-num" data-num="21"></span>    _setBridgeOnMainQueue <span class="token operator">=</span> <span class="token function">RCTIsMainQueue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="22"></span>    <span class="token punctuation">[</span><span class="token keyword">self</span> installLibrary<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="23"></span><span class="token punctuation">}</span>
<span class="ln-num" data-num="24"></span>
<span class="ln-num" data-num="25"></span><span class="token operator">-</span> <span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span>installLibrary <span class="token punctuation">{</span>
<span class="ln-num" data-num="26"></span>
<span class="ln-num" data-num="27"></span>    RCTCxxBridge <span class="token operator">*</span>cxxBridge <span class="token operator">=</span> <span class="token punctuation">(</span>RCTCxxBridge <span class="token operator">*</span><span class="token punctuation">)</span><span class="token keyword">self</span><span class="token punctuation">.</span>bridge<span class="token punctuation">;</span>
<span class="ln-num" data-num="28"></span>
<span class="ln-num" data-num="29"></span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>cxxBridge<span class="token punctuation">.</span>runtime<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="30"></span>       example<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">install</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token punctuation">(</span>facebook<span class="token punctuation">:</span><span class="token punctuation">:</span>jsi<span class="token punctuation">:</span><span class="token punctuation">:</span>Runtime <span class="token operator">*</span><span class="token punctuation">)</span>cxxBridge<span class="token punctuation">.</span>runtime<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="31"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="32"></span><span class="token punctuation">}</span>
<span class="ln-num" data-num="33"></span>
<span class="ln-num" data-num="34"></span><span class="token keyword">@end</span></code></pre></div>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We are telling React that our module requires setup on Main Queue.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We are getting an instance of <code style="color:#910240">bridge</code> which we will use to get the runtime and install our jsi bindings. Inside it we are checking if <code style="color:#cc1491">bridge.runtime</code> exists or not. If it does not, we are waiting for sometime and then trying again until the <code style="color:#cc1491">bridge.runtime</code> becomes available.</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2"><code style="color:#9a03bc">SimpleJsi.h</code></h3>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-objc"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">import</span> <span class="token expression"><span class="token operator">&lt;</span>React<span class="token operator">/</span>RCTBridgeModule<span class="token punctuation">.</span>h<span class="token operator">&gt;</span><span class="token punctuation">;</span></span></span>
<span class="ln-num" data-num="2"></span>
<span class="ln-num" data-num="3"></span><span class="token keyword">@interface</span> SimpleJsi <span class="token punctuation">:</span> NSObject <span class="token operator">&lt;</span>RCTBridgeModule<span class="token operator">&gt;</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="4"></span>
<span class="ln-num" data-num="5"></span><span class="token keyword">@property</span> <span class="token punctuation">(</span>nonatomic<span class="token punctuation">,</span> assign<span class="token punctuation">)</span> BOOL setBridgeOnMainQueue<span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>
<span class="ln-num" data-num="7"></span><span class="token keyword">@end</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are adding a property here, <code style="color:#5daa0f">setBridgeOnMainQueue</code> which tells React to set the bridge on main queue. This results in <code style="color:#0ba565">setBridge</code> being called in our module with the <code style="color:#910240">bridge</code>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">So this is how we configure JSI for both android and iOS. Now let's see what is happening in <code style="color:#c403c4">example.cpp</code> where our <code style="color:#ca0eef">install</code>function is present.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-cpp"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"example.h"</span><span class="token expression"><span class="token punctuation">;</span></span></span>
<span class="ln-num" data-num="2"></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;jsi/jsi.h&gt;</span><span class="token expression"><span class="token punctuation">;</span></span></span>
<span class="ln-num" data-num="3"></span>
<span class="ln-num" data-num="4"></span><span class="token keyword">using</span> <span class="token keyword">namespace</span> facebook<span class="token double-colon punctuation">::</span>jsi<span class="token punctuation">;</span>
<span class="ln-num" data-num="5"></span><span class="token keyword">using</span> <span class="token keyword">namespace</span> std<span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>
<span class="ln-num" data-num="7"></span><span class="token keyword">namespace</span> example <span class="token punctuation">{</span>
<span class="ln-num" data-num="8"></span>    <span class="token keyword">void</span> <span class="token function">install</span><span class="token punctuation">(</span>Runtime <span class="token operator">&amp;</span> jsiRuntime<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="9"></span>        <span class="token keyword">auto</span> helloWorld <span class="token operator">=</span> <span class="token class-name">Function</span><span class="token double-colon punctuation">::</span><span class="token function">createFromHostFunction</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token class-name">PropNameID</span><span class="token double-colon punctuation">::</span><span class="token function">forAscii</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"helloWorld"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">(</span>Runtime <span class="token operator">&amp;</span> runtime<span class="token punctuation">,</span>
<span class="ln-num" data-num="10"></span>            <span class="token keyword">const</span> Value <span class="token operator">&amp;</span> thisValue<span class="token punctuation">,</span>
<span class="ln-num" data-num="11"></span>                <span class="token keyword">const</span> Value <span class="token operator">*</span> arguments<span class="token punctuation">,</span> size_t count<span class="token punctuation">)</span> <span class="token operator">-&gt;</span> Value <span class="token punctuation">{</span>
<span class="ln-num" data-num="12"></span>            string helloworld <span class="token operator">=</span> <span class="token string">"helloworld"</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>            <span class="token keyword">return</span> <span class="token function">Value</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> <span class="token class-name">String</span><span class="token double-colon punctuation">::</span><span class="token function">createFromUtf8</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> helloworld<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="14"></span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="15"></span>
<span class="ln-num" data-num="16"></span>        jsiRuntime<span class="token punctuation">.</span><span class="token function">global</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"helloWorld"</span><span class="token punctuation">,</span> <span class="token function">move</span><span class="token punctuation">(</span>helloWorld<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="17"></span>    <span class="token punctuation">}</span>
<span class="ln-num" data-num="18"></span><span class="token punctuation">}</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Okay let's make this consumable.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">At the top, you see that we have included <code style="color:#dd139a">jsi</code> include files.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The <code style="color:#c111b3">using namespace facebook::jsi;</code> etc helps us not write <code style="color:#e80686">facebook::jsi</code> over and over.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ca0eef">install</code> function takes one parameter and that is our JS runtime. Inside this function we are registering a method by name <code style="color:#049185">helloWorld</code> which will return a <code style="color:#c1079f">hello world</code> string when we call it from javascript code.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#bf0095">Function::createFromHostFunction</code> is a method creates a function which, when invoked, calls C++ code.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ce0ab7">jsiRuntime.global().setProperty</code> is where we bind our function with the javascript runtime global object.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Function::createFromHostFunction(Runtime, PropNameID, paramCount, function)</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#b50c4a">Runtime</code>: Represents a JS runtime where our javascript code is running</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#9e0c2e">PropNameID</code>: An identifier to find our function. It is a simple string.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#487204">paramCount</code>: Number of parameters this function will have. In our case it's <code style="color:#d60a99">0</code>.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#ba12ed">function</code>: A function that will be invoked when we call <code style="color:#e212ed">global.helloWorld()</code> from javascript.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Our <code style="color:#ba12ed">function</code> has also 4 parameters.</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#b50c4a">Runtime</code>: Represents a JS runtime where our javascript code is running</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#e2890b">Value &amp;thisValue</code>: It is a reference to <code style="color:#870237">Value</code> class instance which is used to pass JS values to and from javascript code.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#03676b">Value *arguments</code>: The arguments for this function coming from Javascript.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><code style="color:#d806ae">size_t count</code>: Total number of arguments.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Inside the function we are creating a simple string <code style="color:#c1079f">hello world</code>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Then we are returning <code style="color:#870237">Value</code>. The <code style="color:#c90491">String::createFromUtf8</code> function helps us convert c++ string(<code style="color:#af0a4c">std::string</code>) to a Javascript String (<code style="color:#cd02e8">jsi::String</code>) value.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Calling our function in Javascript</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Now we can call our function <code style="color:#049185">helloWorld</code> in javascript code. This should show helloworld at the center of screen.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">function</span> <span class="token function"><span class="token maybe-class-name">App</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="2"></span>  <span class="token keyword">const</span> <span class="token punctuation">[</span>result<span class="token punctuation">,</span> setResult<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token property-access">useState</span><span class="token operator">&lt;</span>number <span class="token operator">|</span> <span class="token keyword nil">undefined</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="3"></span>
<span class="ln-num" data-num="4"></span>  <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span>    <span class="token function">setResult</span><span class="token punctuation">(</span>global<span class="token punctuation">.</span><span class="token method function property-access">helloWorld</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="ln-num" data-num="6"></span>  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>
<span class="ln-num" data-num="8"></span>  <span class="token keyword control-flow">return</span> <span class="token punctuation">(</span>
<span class="ln-num" data-num="9"></span>    <span class="token operator">&lt;</span><span class="token maybe-class-name">View</span> style<span class="token operator">=</span><span class="token punctuation">{</span>styles<span class="token punctuation">.</span><span class="token property-access">container</span><span class="token punctuation">}</span><span class="token operator">&gt;</span>
<span class="ln-num" data-num="10"></span>      <span class="token operator">&lt;</span><span class="token maybe-class-name">Text</span><span class="token operator">&gt;</span><span class="token maybe-class-name">Result</span><span class="token operator">:</span> <span class="token punctuation">{</span>result<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token maybe-class-name">Text</span><span class="token operator">&gt;</span>
<span class="ln-num" data-num="11"></span>    <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token maybe-class-name">View</span><span class="token operator">&gt;</span>
<span class="ln-num" data-num="12"></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span><span class="token punctuation">}</span></code></pre></div>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/phone_screen.C8X5fbTV.webp 400w, https://blog.notesnook.com/assets/static/phone_screen.DYS9qYH_.webp 600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/phone_screen.DORDoUn8.png 400w, https://blog.notesnook.com/assets/static/phone_screen.vQgbbFu0.png 600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/phone_screen.DORDoUn8.png" srcset="https://blog.notesnook.com/assets/static/phone_screen.DORDoUn8.png 400w, https://blog.notesnook.com/assets/static/phone_screen.vQgbbFu0.png 600w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="JSI module running in react native app" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">From here onwards, there are unlimited possibilities to what you can do.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Calling function with multiple arguments</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In <code style="color:#c403c4">example.cpp</code> add this new function. It's a simple function that does multiplication of two numbers</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-cpp"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">auto</span> multiply <span class="token operator">=</span> <span class="token class-name">Function</span><span class="token double-colon punctuation">::</span><span class="token function">createFromHostFunction</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="2"></span>    jsiRuntime<span class="token punctuation">,</span> <span class="token class-name">PropNameID</span><span class="token double-colon punctuation">::</span><span class="token function">forAscii</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"multiply"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span>    <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">(</span>Runtime <span class="token operator">&amp;</span>runtime<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">&amp;</span>thisValue<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">*</span>arguments<span class="token punctuation">,</span>
<span class="ln-num" data-num="4"></span>       size_t count<span class="token punctuation">)</span> <span class="token operator">-&gt;</span> Value <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span>      <span class="token keyword">int</span> x <span class="token operator">=</span> arguments<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>      <span class="token keyword">int</span> y <span class="token operator">=</span> arguments<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>      <span class="token keyword">return</span> <span class="token function">Value</span><span class="token punctuation">(</span>x <span class="token operator">*</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span>
<span class="ln-num" data-num="10"></span>jsiRuntime<span class="token punctuation">.</span><span class="token function">global</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"multiply"</span><span class="token punctuation">,</span> <span class="token function">move</span><span class="token punctuation">(</span>multiply<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notice now that we have set <code style="color:#487204">paramCount</code> to 2 because we have two arguments.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In Javascript we can call</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-javascript"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span>global<span class="token punctuation">.</span><span class="token method function property-access">multiply</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 8</span></code></pre></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Calling a JS Callback from C++</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Here we are doing the same multiplication but not returning its value. Instead we are calling a JS function.</p>
<div class="d_flex w_mobileFull sm:w_full ov_hidden flex-d_column bdr_default pos_relative mb_4 [&amp;:hover_.code-copy]:d_flex bd_default"><p class="c_white d_none pos_absolute top_1 right_1 fs_s bg-c_[#5b6577] as_flex-end p_1 bdr_sm mr_1 mt_1 cursor_pointer z_2 ai_center [&amp;:hover]:filter_[brightness(90%)] code-copy" style="font-size:var(--font-sizes-sm)"></p><div class="d_flex flex-sh_0 jc_center ai_center"><svg viewBox="0 0 24 24" style="stroke-width:0px;stroke:var(--theme-ui-colors-muted);width:18px;height:18px" role="presentation" class="icon"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" style="fill:var(--theme-ui-colors-muted)"></path></svg></div><p></p><pre class="d_flex py_3 pr_3 ov-x_auto ov-y_auto max-h_[90vh] fs_s codeblock"><code class="language-cpp"><span class="ln-bg"></span><span class="ln-num" data-num="1"></span><span class="token keyword">auto</span> multiplyWithCallback <span class="token operator">=</span> <span class="token class-name">Function</span><span class="token double-colon punctuation">::</span><span class="token function">createFromHostFunction</span><span class="token punctuation">(</span>
<span class="ln-num" data-num="2"></span>    jsiRuntime<span class="token punctuation">,</span> <span class="token class-name">PropNameID</span><span class="token double-colon punctuation">::</span><span class="token function">forAscii</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"multiplyWithCallback"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="3"></span>    <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">(</span>Runtime <span class="token operator">&amp;</span>runtime<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">&amp;</span>thisValue<span class="token punctuation">,</span> <span class="token keyword">const</span> Value <span class="token operator">*</span>arguments<span class="token punctuation">,</span>
<span class="ln-num" data-num="4"></span>       size_t count<span class="token punctuation">)</span> <span class="token operator">-&gt;</span> Value <span class="token punctuation">{</span>
<span class="ln-num" data-num="5"></span>      <span class="token keyword">int</span> x <span class="token operator">=</span> arguments<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="6"></span>      <span class="token keyword">int</span> y <span class="token operator">=</span> arguments<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="7"></span>      arguments<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getObject</span><span class="token punctuation">(</span>runtime<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getFunction</span><span class="token punctuation">(</span>runtime<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> x <span class="token operator">*</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="8"></span>      <span class="token keyword">return</span> <span class="token function">Value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="9"></span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="10"></span>
<span class="ln-num" data-num="11"></span>jsiRuntime<span class="token punctuation">.</span><span class="token function">global</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span>jsiRuntime<span class="token punctuation">,</span> <span class="token string">"multiplyWithCallback"</span><span class="token punctuation">,</span>
<span class="ln-num" data-num="12"></span>                                <span class="token function">move</span><span class="token punctuation">(</span>multiplyWithCallback<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="ln-num" data-num="13"></span>
<span class="ln-num" data-num="14"></span>While in javascript<span class="token punctuation">,</span> we can call the function like <span class="token keyword">this</span><span class="token operator">:</span>
<span class="ln-num" data-num="15"></span>
<span class="ln-num" data-num="16"></span>  global<span class="token punctuation">.</span><span class="token function">multiplyWithCallback</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
<span class="ln-num" data-num="17"></span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 8</span>
<span class="ln-num" data-num="18"></span>  <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2"><code style="color:#870237">Value</code></h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">A Value can be <code style="color:#cb0be0">undefined</code>, <code style="color:#bf1363">null</code>, <code style="color:#bd03c6">boolean</code>, <code style="color:#e00b9c">number</code>, <code style="color:#d30c9b">symbol</code>, <code style="color:#d60ed2">string</code>, or <code style="color:#ed109f">object</code>.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Conclusion</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">JSI is a game changer for React Native and and it is transforming the way React Native works. Today we have learnt how to build a simple JSI module. <a href="https://blog.notesnook.com/convert-native-modules-to-react-native-jsi-modules/" target="_blank" class="c_accent">In the next blog, I explain how to convert any Native Module to a React Native JSI module using some simple steps.</a></p>
<blockquote class="d_flex flex-d_column jc_center fs_lg bg_muted bd-l_[5px_solid_var(--colors-info)] ml_6 bdr_default px_3">
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can find the complete code for the library + examples <a href="https://github.com/ammarahm-ed/react-native-simple-jsi" target="_blank" class="c_accent">on Github</a>. React Native JSI is being actively used for encryption &amp; storage inside the Notesnook Android &amp; iOS apps. You can get the apps from <a href="https://notesnook.com/" target="_blank" class="c_accent">here</a>.</p>
</blockquote>]]></content:encoded>
            <author>Ammar Ahmed</author>
            <category>Development</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.B9X5PIpS.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Notesnook v1.4: Monographs, UI Updates, and Yearly Plan]]></title>
            <link>https://blog.notesnook.com/notesnook-1-4-update</link>
            <guid isPermaLink="false">https://blog.notesnook.com/notesnook-1-4-update</guid>
            <pubDate>Sat, 26 Jun 2021 12:01:22 GMT</pubDate>
            <description><![CDATA[This release brings note publishing via Monographs, many UI improvements, bug fixes, and a new yearly plan.]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">After months of bug fixes and minor improvements, we are finally adding new stuff. This release took weeks to finalize but it was worth it.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Monographs</h2>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/monograph.BgC-RJUW.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/monograph.D2EfNzCR.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/monograph.D2EfNzCR.png" srcset="https://blog.notesnook.com/assets/static/monograph.D2EfNzCR.png 640w" sizes="100vw" alt="Monographs" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Sharing a note with someone can be such a tedious task. You have to copy/download/export it to a file and upload it to some cloud storage and then attach it in an email and send it to them.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With Notesnook, you don't need to do that anymore. Monographs enable you to share your notes with anyone in just one click. Once a note is published as a monograph, you get a public url which you can send to the person who needs access to information in the note.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You can share this url with anyone to view the contents of the note without downloading Notesnook. And the cool part is, you can encrypt(lock) monographs with a password.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">How cool is that?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">An example of this same changelog published as a Monograph is <a href="https://monogr.ph/b6538967b77d81651c8a9a12" target="_blank" class="c_accent">available here</a>.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Context menus now have icons</h2>
<div sx="[object Object]" class="d_flex"><div sx="[object Object]" class=""></div><p sx="[object Object]" style="font-size:var(--font-sizes-sm)" class="c_paragraph">You can never have enough icons.</p><div sx="[object Object]" class=""></div></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Icons add visual context helping users perform actions faster. That is why, all context menus will have icons by default.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/context_menu.LJpIC3Nv.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/context_menu.DdpBO7Nu.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/context_menu.DdpBO7Nu.png" srcset="https://blog.notesnook.com/assets/static/context_menu.DdpBO7Nu.png 640w" sizes="100vw" alt="Context menus" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Sticky editor toolbar</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Going up and down just to add an image or a table was getting annoying. So we made the toolbar sticky. Moreover, the toolbar is now above the note title. This makes the editor much more "flowy".</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/sticky_toolbar.BYKoKFKd.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/sticky_toolbar.BrNrVG-C.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/sticky_toolbar.BrNrVG-C.png" srcset="https://blog.notesnook.com/assets/static/sticky_toolbar.BrNrVG-C.png 640w" sizes="100vw" alt="Monographs" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Payments</h2>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/payments.CJfhnfRX.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/payments.F0-n5ori.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/payments.F0-n5ori.png" srcset="https://blog.notesnook.com/assets/static/payments.F0-n5ori.png 640w" sizes="100vw" alt="Monographs" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Yearly subscription plan</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You asked, we listened. There is now an official <strong>49.99 USD/yr plan for Notesnook Pro</strong>. You get all the same perks as the monthly plan except you pay yearly instead of monthly.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Super new subscription dialog</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Aside from a super new Notesnook Pro dialog, we added localized pricing to help you make the final decision faster. You can also apply promo codes directly from inside the dialog.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Try the new version</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">As always, all these features are 100% cross-platform. You can install our Android &amp; iOS apps from their respective stores.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For desktop, you can go and <a href="https://notesnook.com/" target="_blank" class="c_accent">download for your platform from our website</a>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For quickly trying out the new version, you can just <a href="https://app.notesnook.com/" target="_blank" class="c_accent">use our web app.</a></p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Stay updated</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We recommend that you <a href="https://twitter.com/notesnook" target="_blank" class="c_accent">follow us on Twitter</a> and <a href="https://discord.com/invite/zQBK97EE22" target="_blank" class="c_accent">join our Discord server</a> to stay up to date with all the new features coming to Notesnook. We also provide 24/7 support to all our users on Discord.</p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.0EE9HT3Q.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Why We Made Another Note Taking App?]]></title>
            <link>https://blog.notesnook.com/why-another-note-taking-app</link>
            <guid isPermaLink="false">https://blog.notesnook.com/why-another-note-taking-app</guid>
            <pubDate>Sun, 20 Jun 2021 12:01:22 GMT</pubDate>
            <description><![CDATA[There are many online notepad and note taking apps. All can be used for writing notes. But none care about privacy of data. Notesnook is here to change that. ]]></description>
            <content:encoded><![CDATA[<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Give someone a notebook and a pen, and ask them to write a note. How long do you think it'll take them? 5 minutes? 10 minutes? They will draw a mind map or a flow chart, label it, draw connections between smaller concepts; all using just a pen and a notebook. It's the ideal way, the standard, in terms of speed &amp; simplicity, for note taking.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">However, for all it's perfection, a physical notebook is not without its drawbacks:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">There's only 1 notebook (unless you take the hassle to create copies); if you lose it, you lose everything in it.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Shabby security - you could lock it with a pin or a lock but that won't stop an intruder from breaking the lock and reading everything inside.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Limit on how many you can carry - depending on the type of productivity enthusiast you are, you might have different notebooks for different things. You can put them all into a sack and carry it with you everywhere but really, how feasible is that?</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Your notes are always in danger due to wear &amp; tear.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">You can't share specific notes without putting your whole notebook at risk.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Online notepad apps try to solve some of these problems - each bringing its own variation of "the ideal way" of writing notes. Some like <a href="https://www.notion.so/" title="Notion" target="_blank" class="c_accent">Notion</a> kill simplicity and instead focus on flexibility; the result is a complex note taking app that does a lot of things but only if you know how to use it. Others like <a href="https://simplenote.com/" title="Simplenote" target="_blank" class="c_accent">Simplenote</a> take the opposite side: simplicity over flexibility. The list is never ending.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Table of Contents</h3>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fg" target="_blank" class="c_accent">So why another note taking app?</a>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fh" target="_blank" class="c_accent">Why was it necessary to make a new note taking app?</a>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fi" target="_blank" class="c_accent">Simple privacy</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fj" target="_blank" class="c_accent">Flexible notes organization system</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fk" target="_blank" class="c_accent">100% zero knowledge seamless data syncing</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fl" target="_blank" class="c_accent">An open platform for your notes</a></li>
</ol>
</li>
</ul>
</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7kvg5l41fv" target="_blank" class="c_accent">Our pricing (or why Notesnook is not FOSS)</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fn" target="_blank" class="c_accent">Who are we?</a>
<ul style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_4">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fo" target="_blank" class="c_accent">Our principles</a>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fp" target="_blank" class="c_accent">User privacy must be the default</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fq" target="_blank" class="c_accent">Make all the opt-out features easy to access and use</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fr" target="_blank" class="c_accent">Never add anything to lock-in the user</a></li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481fs" target="_blank" class="c_accent">User feedback is more important than our own ideas</a></li>
</ol>
</li>
</ul>
</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0"><a href="https://blog.notesnook.com/why-another-note-taking-app/#mcetoc_1f7ksrt481ft" target="_blank" class="c_accent">Our plans and the future of Notesnook</a></li>
</ul>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">So why another note taking app?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Privacy is such an important issue in note taking that I am baffled by why it isn't more mainstream. Go back several centuries when there were no computers and you find state officials, and other people who cared about their notes, <a href="https://en.wikipedia.org/wiki/History_of_cryptography" title="History of cryptography - Wikipedia" target="_blank" class="c_accent">encrypting everything manually using various ciphers</a>. This was a widespread practice when sending messages via pigeons or by hand. <a href="https://us-cert.cisa.gov/ncas/tips/ST04-019" title="Understanding Encryption" target="_blank" class="c_accent">The purpose of all this</a> was to ensure that only the intended person could read their messages and anyone who'd violate this would be severely punished.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Fast forward to 2021 and majority of the apps are still storing their users' data without any form of encryption - meaning that anyone with access to the apps' servers could read, edit, delete, or sell that data.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Some apps like <a href="https://standardnotes.org/" title=" Standard Notes" target="_blank" class="c_accent">Standard Notes</a> tried to change this by incorporating client side data encryption (meaning everything got encrypted on your device offline). While this kept your data safe, it came with a catch; you had to sacrifice useful features like integrations with other apps, notes via email, data recovery etc.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>Why?</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Back in November of 2019 when Notesnook was still a dream, we asked this very question. Why do we have to sacrifice convenience, ease of use, features, and our time to achieve privacy (which is supposed to be <a href="https://www.forbes.com/sites/forbestechcouncil/2019/11/12/data-privacy-as-a-basic-human-right/" title="Data Privacy As A Basic Human Right" target="_blank" class="c_accent">our basic human right</a>)? And so Notesnook was born.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/notesnook_devices.BcikiHXy.webp 640w" sizes="100vw"><source type="image/jpg" srcset="https://blog.notesnook.com/assets/static/notesnook_devices.AbpqSduv.jpg 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/notesnook_devices.AbpqSduv.jpg" srcset="https://blog.notesnook.com/assets/static/notesnook_devices.AbpqSduv.jpg 640w" sizes="100vw" alt="Notesnook works on all platforms with a seamless sync." style="max-width:640px" class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">Notesnook works on all platforms with a seamless sync.</figcaption></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We had a simple checklist in mind:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">No compromise on security and user privacy.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">No sacrifice on important note taking features (e.g. note locking, attachments, exports, robust organization, integrations with other apps, customization, feature rich text editor etc.).</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Provide a simple, easy to use user interface.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">~99% feature parity across platforms.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Zero user lock-in - use standard formats and allow users to export/backup to any format they want.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">And of course, encrypted notes syncing that actually worked.</li>
</ol>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Why was it necessary to make a new note taking app?</h3>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">1. Simple privacy</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">When I was doing idea validation for Notesnook, a lot of privacy enthusiastic people <a href="https://news.ycombinator.com/item?id=24764577" target="_blank" class="c_accent">used multiple apps</a> <a href="https://www.reddit.com/r/Evernote/comments/ibh327/alright_folks_how_are_we_feeling_about_evernote/g1ycpvl/?utm_source=reddit&amp;utm_medium=web2x&amp;context=3" target="_blank" class="c_accent">for taking notes</a>: X for syncing between devices, Y for personal notes, Z for quick checklists and bookmarks etc., or they'd be using offline apps that synced via their own cloud server or something of that sort.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">For an average Joe interested in privacy, all this is way too complicated. If all you want is to take a quick note about a product you saw on the Internet and have it on all your devices when you go looking for it in the market, you shouldn't have to jump a thousand hoops and run a marathon. <strong>Privacy should not have such a high cost.</strong></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Taking a note on Notesnook takes <em>a single tap</em> and guess what? Everything is private, encrypted, and synced by default i.e., you have to press 0 buttons, toggle 0 settings, and write 0 commands to get it working. It. Just. Works.</p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">2. Flexible notes organization system</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Aside from privacy and simplicity, we wanted to have a 3-way organization system via notebooks, tags &amp; colors. None of the note taking apps we have tested (the top 10 of them at least) had anything similar.</p>
<style data-emotion="css fsju5r">.css-fsju5r{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><style data-emotion="css xmhgif">.css-xmhgif{box-sizing:border-box;margin:0;min-width:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><div class="css-xmhgif"><p style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><p style="font-weight:400;line-height:2rem;letter-spacing:0.4px"></p><h4 style="font-size:var(--font-sizes-h5);margin-top:0;margin-bottom:10px;padding-top:0" class="fw_heading c_heading ta_left mb_1">Why is this important?</h4>Having multiple ways to organize notes makes more sense. You can use colors to quickly priortize your notes, tags for categorization, and notebooks &amp; topics for something more specific &amp; subject related. Or you can combine all three for maximum flexibility.<p></p><p></p></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Instead of complex infinite hierarchies for organizing notes, we went for link based notebook organization - this allowed for 1 note to exist under multiple notebooks and topics: creating a web of sorts. Add tags and colors on top of it and it makes for an extremely flexible yet simple organization system.</p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">3. 100% zero knowledge seamless data syncing</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There are multiple notes apps that sync across devices (although <a href="https://www.reddit.com/r/StandardNotes/comments/7po3ah/my_review_of_standard_notes/" title="A review of Standard Notes" target="_blank" class="c_accent">multi platform compatibility is not a priority in any of them</a> - looking at EverNote &amp; Standard Notes) but syncing is useless if you can't actually do anything with the synced data. For example, what's the point of syncing notes between devices if you can't edit some of them; only view them?</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">No note taking app to date has ~99% feature parity across platforms - this is understandable as it takes a lot of work to develop apps for separate platforms.</p>
<style data-emotion="css fsju5r">.css-fsju5r{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><style data-emotion="css xmhgif">.css-xmhgif{box-sizing:border-box;margin:0;min-width:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><div class="css-xmhgif"><p style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><p style="font-weight:400;line-height:2rem;letter-spacing:0.4px"></p><h4 style="font-size:var(--font-sizes-h5);margin-top:0;margin-bottom:10px;padding-top:0" class="fw_heading c_heading ta_left mb_1">Why is this important?</h4>Most of us have at least 2 devices: a computer and a phone. Apps that don't offer syncing or offer syncing only for specific platforms hinder your productivity. Imagine getting an idea while you are on a bus. You quickly note it down on your phone and later in the day open your laptop to finish it. Seamless. Fluid.<p></p><p></p></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">In Notesnook we did just that. We developed a syncing system that was 100% zero knowledge and <em>also</em> 100% compatible on all platforms. This meant that you can edit a note on your Macbook and continue editing it on your iPhone later on with all the formatting intact. In fact, all platforms feature the exact same rich text editor.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The cool thing about this is that everything gets encrypted on your device offline. Notesnook uses zero knowledge encryption to make sure no one aside from you can access or read your data.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2"><strong>The keys to all your data remain with you and no one can access your data without those keys.</strong></p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/zero-knowledge-diagram-2.DoVNNVMR.webp 640w, https://blog.notesnook.com/assets/static/zero-knowledge-diagram-2.Z4sEpNvg.webp 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/zero-knowledge-diagram-2.DOHjelPQ.png 640w, https://blog.notesnook.com/assets/static/zero-knowledge-diagram-2.CE_pVJFk.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw"><img src="https://blog.notesnook.com/assets/static/zero-knowledge-diagram-2.DOHjelPQ.png" srcset="https://blog.notesnook.com/assets/static/zero-knowledge-diagram-2.DOHjelPQ.png 640w, https://blog.notesnook.com/assets/static/zero-knowledge-diagram-2.CE_pVJFk.png 1024w" sizes="(min-width: 1600px) 72vw, (min-width: 1024px) 80vw, (min-width: 640px) 88vw, 92vw" alt="How zero knowledge encryption works." class="bdr_default w_full"></picture><figcaption class="w_full ta_center font-style_italic fs_s c_info">How zero knowledge encryption works.<!-- --> <!-- -->-<!-- --> <a class="c_info">credits: Techferno</a></figcaption></figure></div>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">4. An open platform for your notes</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Most of the note taking apps (Bear, Google Keep, Notion, EverNote, OneNote) use proprietary formats making <a href="https://www.reddit.com/r/Evernote/comments/6od9z1/why_are_people_still_using_evernote/dkhwxih/?utm_source=reddit&amp;utm_medium=web2x&amp;context=3" target="_blank" class="c_accent">migration</a> <a href="https://discussion.evernote.com/forums/topic/131587-exporting-all-notes-no-longer-possible/" target="_blank" class="c_accent">to other</a> apps impossible or very hard (not to worry though, <a href="https://importer.notesnook.com/" title="Notesnook Importer" target="_blank" class="c_accent">we made our very own Importer</a> that works around all the quirks and imports your data into Notesnook with almost 100% compatibility in just a couple of clicks).</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Usage of open formats is important in case the app gets abandoned or you decide to move away. While not open source, Notesnook makes use of open &amp; standard formats like HTML, JSON, Markdown etc. making sure you can edit and view your notes without needing special tools.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Even the encryption algorithms we have used are openly available and you could decrypt your notes using any tool that supports them.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Our pricing (or why Notesnook is not FOSS)</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">While we love FOSS and have extensively made use of FOSS libraries and tools (TinyMCE editor, libsodium and many other React libraries), we could not go the FOSS route due to a couple of reasons:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">The developement cost required to maintain and support the app could not be covered with a sponsor/donations only business model.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Server costs and other behind-the-scenes paid services forced us to adopt a subscription model - we don't want to shut our services due to low budget.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">We also wanted to give 1st class support to our customers and that kind of dedication is not possible (or sustainable) for a long time based on donations.</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Keep these reasons in mind, we calculated an average price based on our competitors and lowered it by 20%. The result was 4.49 USD per month.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are <strong>the only</strong> cross-platform, zero knowledge note taking service that offers a monthly plan at 4.49 USD. Even if you self host, you have to pay at least 5.00 USD per month. This pricing helps us maintain and support Notesnook while remaining affordable.</p>
<style data-emotion="css fsju5r">.css-fsju5r{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><style data-emotion="css xmhgif">.css-xmhgif{box-sizing:border-box;margin:0;min-width:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><div class="css-xmhgif"><p style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><h4 style="margin-top:0;padding-top:0;margin-bottom:10px">Yearly plan</h4><p style="line-height:2rem;letter-spacing:0.4px;font-weight:400;margin-bottom:0;padding-bottom:0">We are planning on launching a similarly affordable yearly plan. <a href="https://twitter.com/notesnook" style="color:var(--theme-ui-colors-primary)" target="_blank" class="c_accent">Follow us on Twitter</a> to stay updated.</p><p></p></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Aside from the monthly plan, we added a freemium Basic plan (with 14-day free Pro trial) that allows you to evaluate Notesnook at your liesure.</p>
<style data-emotion="css fsju5r">.css-fsju5r{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><style data-emotion="css xmhgif">.css-xmhgif{box-sizing:border-box;margin:0;min-width:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><div class="css-xmhgif"><p style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><p style="font-weight:400;line-height:2rem;letter-spacing:0.4px"><a href="https://app.notesnook.com/#/buy/INTRO50" sx="[object Object]" target="_blank" class="c_accent">Subscribe now to get 50% off</a> first month of Notesnook pro.</p><p></p></div>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Who are we?</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We are a small team (just 3 people) based in Punjab, Pakistan. Our goal is to simplify privacy and prove that even the most complex apps can be built with user privacy as the default.</p>
<h3 style="font-size:var(--font-sizes-h4)" class="fw_heading c_heading ta_left mb_2">Our principles</h3>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Most services nowadays are business or revenue focused; from complicating opt-out processes like unsubscribing, exporting, or deleting an account to completely ignoring user feedback the consumer is consistently ignored. This makes sense in a purely revenue focused business model but it has no foundation in principles that benefit the world.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Having no core principles to guide them most businesses follow the crowd and implement features and flows copied from the "most successful" services. The result are apps that benefit the business; not the user.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We built Notesnook with concrete principles in mind:</p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">1. User privacy must be the default</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">There are <a href="https://support.apple.com/guide/security/secure-features-in-the-notes-app-sec1782bcab1/web" target="_blank" class="c_accent">several</a> <a href="https://joplinapp.org/e2ee/" target="_blank" class="c_accent">note taking</a> <a href="https://bear.app/faq/How%20to%20encrypt%20&amp;%20lock%20notes%20with%20Bear/" target="_blank" class="c_accent">apps</a> that offer encryption as a "feature". For us, encryption and privacy are the default. We have no business reading your notes no matter if you are a free user or have bought our subscription.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We cannot promote privacy by making it a premium feature only accessible to those who pay. Your privacy is your right - not a bargain or a compromise.</p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">2. Make all the opt-out features easy to access and use</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Every day more and more services follow <a href="https://www.darkpatterns.org/" title="Dark Patterns" target="_blank" class="c_accent">the "dark UX patterns"</a> - basically making opt-out flows impossible to access.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">You want to delete the account? Sure, you can do that but you have to contact our customer support then verify yourself and even after that we'll keep your data for N number of days just in case.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Why? Why isn't it just a button or two away? <a href="https://www.journalofaccountancy.com/issues/2018/sep/how-to-delete-amazon-account.html" target="_blank" class="c_accent">Why companies like Amazon &amp; Facebook complicate account deactivation/downgrading procedures?</a></p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The other case in point is telemetry. It is important to know which features are being used the but no one ever makes it clear. In Notesnook, we have included a clear list of everything we track. Turning all telemetry off is just a toggle away.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/telemetry_1.CerJLjZW.webp 406w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/telemetry_1.BoFVeIT4.png 406w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/telemetry_1.BoFVeIT4.png" srcset="https://blog.notesnook.com/assets/static/telemetry_1.BoFVeIT4.png 406w" sizes="100vw" style="max-width:406px" class="bdr_default w_full"></picture></figure><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/telemetry_2.D_FiIaxB.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/telemetry_2.oQ1hxc5q.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/telemetry_2.oQ1hxc5q.png" srcset="https://blog.notesnook.com/assets/static/telemetry_2.oQ1hxc5q.png 640w" sizes="100vw" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Want to delete your account with all it's data? Press a button, input your account password and that's it.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Want to downgrade your account to Basic? It's just 1 or 2 button presses away.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We have tried to make everything as clear and straight forward as possible because <strong>privacy doesn't just include encryption but also clarity, freedom, and openness.</strong></p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">3. Never add anything to lock-in the user</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Your data is your data. If you decide to one day leave our service, exporting your data takes just one click. The important thing is that the export/backup format is standard JSON/HTML so you can easily preview or import this data into any other app.</p>
<div sx="[object Object]" class="d_flex"><figure class="w_full m_0 d_flex flex-d_column ai_center"><picture class="d_flex ov_hidden m_0 p_0 ai_center jc_center"><source type="image/webp" srcset="https://blog.notesnook.com/assets/static/export_notes.DF4p8j_w.webp 640w" sizes="100vw"><source type="image/png" srcset="https://blog.notesnook.com/assets/static/export_notes.Boir6wr2.png 640w" sizes="100vw"><img src="https://blog.notesnook.com/assets/static/export_notes.Boir6wr2.png" srcset="https://blog.notesnook.com/assets/static/export_notes.Boir6wr2.png 640w" sizes="100vw" alt="Export notes in pdf, markdown, html" style="max-width:640px" class="bdr_default w_full"></picture></figure></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We trust in the uniqueness of our app instead of putting locks to keep you in.</p>
<h4 style="font-size:var(--font-sizes-h5)" class="fw_heading c_heading ta_left mb_1">4. User feedback is more important than our own ideas</h4>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">If a large enough user base is requesting a feature then who are we to deny it? We truly believe in user democracy. That is why we have opened all social channels to listen to you - reach out to us at any time with any idea and we'll give it its due attention.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Building a better product isn't just about features but also communication.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Our plans and the future of Notesnook</h2>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">We laid the <a href="https://notesnook.com" title="Notesnook | A private note taking app" target="_blank" class="c_accent">basic groundwork for Notesnook</a> in the first major version. This included all the (private) note taking essentials like:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Organization using notebooks, tags &amp; colors</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Search</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Reasonably good rich text editor</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Cross-platform syncing with encryption</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Note exports</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Backup &amp; restore</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Search</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">And some privacy extras like:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Secure vaults for notes</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">App lock (only on mobile for now)</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">However, this is just the beginning. We are already working on these features that'll upgrade the whole note taking experience:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Note publishing - a simple way to share any note to a publicly viewable URL.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Multi-window support - ability to open multiple instances of Notesnook in browser tabs or windows and have them work in sync.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Encrypted file attachments - this includes any type of attachments including audio, PDF, docx etc.</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">VSCode-like quick open - a faster way to navigate between notes</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Item linking - reference any item (notebook, tag, or note) directly inside a note</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Editor slash commands - help formatting commands just a key press away</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With these features on our roadmap:</p>
<ol style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 p_0 mb_4 ml_4 li-s_inside li-t_numeric">
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Global search</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">At rest encryption of all data</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Sync over Websockets</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Reminders</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Lists</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">Links</li>
<li style="font-size:var(--font-sizes-sm)" class="c_paragraph m_0 mb_1 pb_0">In-app tabs support (to open multiple notes side by side)</li>
</ol>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">The future of Notesnook is bright. You can <a href="https://docs.notesnook.com/roadmap/" title="Notesnook Roadmap" target="_blank" class="c_accent">track our roadmap here</a>.</p>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">With each release we will move towards making Notesnook the defacto personal note taking app. You can follow the development process and suggest other cool ideas by giving us a <a href="https://twitter.com/notesnook" title="Notesnook on Twitter" target="_blank" class="c_accent">follow on Twitter</a> and joining <a href="https://discord.gg/5davZnhw3V" title="Notesnook Discord Server" target="_blank" class="c_accent">our Discord</a> community.</p>
<h2 style="font-size:var(--font-sizes-h2)" class="fw_heading c_heading mb_4 ta_left">Try Notesnook</h2>
<style data-emotion="css fsju5r">.css-fsju5r{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><style data-emotion="css xmhgif">.css-xmhgif{box-sizing:border-box;margin:0;min-width:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:backgroundSecondary;padding:25px;border-radius:15px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:64px;margin-bottom:64px;}</style><div class="css-xmhgif"><p style="font-size:var(--font-sizes-sm)" class="c_paragraph"></p><p style="font-weight:400;line-height:2rem;letter-spacing:0.4px">Get 15% off yearly &amp; monthly plan. Use coupon code <strong> YEAR15. </strong><a href="https://app.notesnook.com/#/buy/YEAR15" sx="[object Object]" target="_blank" class="c_accent"> Subscribe now</a></p><p></p></div>
<p style="font-size:var(--font-sizes-sm)" class="c_paragraph my_2">Notesnook is available on all platforms: <a href="https://notesnook.com/" title="Download Notesnook" target="_blank" class="c_accent">Web, Linux, macOS, Windows, Android &amp; iOS.</a></p>]]></content:encoded>
            <author>Abdullah Atta</author>
            <category>Notesnook</category>
            <enclosure url="https://blog.notesnook.com/assets/static/image.6-9mYqui.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>